@zackbart/connecta 0.36.2 → 0.37.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/bin/version.mjs +1 -1
  3. package/dist/auth/downstream-oauth.js +31 -12
  4. package/dist/auth/static-oauth.js +3 -2
  5. package/dist/auth/stored-observation.js +8 -0
  6. package/dist/call-admission.js +27 -14
  7. package/dist/connectors/api-connector.js +4 -0
  8. package/dist/connectors/option-shapes.js +5 -1
  9. package/dist/connectors/remote-mcp.d.ts +29 -3
  10. package/dist/connectors/remote-mcp.js +142 -22
  11. package/dist/execute.js +5 -5
  12. package/dist/executor-admission.js +27 -13
  13. package/dist/executors/quickjs.js +4 -1
  14. package/dist/index.d.ts +2 -1
  15. package/dist/invocation.js +6 -7
  16. package/dist/operator-ui/generated.js +2 -2
  17. package/dist/operator-ui/model.d.ts +2 -0
  18. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  19. package/dist/request-principal.js +34 -0
  20. package/dist/request-token.d.ts +46 -0
  21. package/dist/routes/mcp.js +6 -6
  22. package/dist/routes/ui-config.js +38 -9
  23. package/dist/routes/ui-data.js +30 -5
  24. package/dist/routes/ui.js +10 -2
  25. package/dist/runtime/settlement.js +59 -0
  26. package/dist/version.d.ts +1 -1
  27. package/dist/version.js +1 -1
  28. package/dist/worker.js +4 -1
  29. package/documentation/architecture.md +68 -25
  30. package/documentation/auth.md +129 -11
  31. package/documentation/code-mode.md +25 -5
  32. package/documentation/integrating.md +18 -4
  33. package/documentation/operator-tests.md +19 -0
  34. package/documentation/operator-ui.md +57 -46
  35. package/examples/worker/README.md +5 -1
  36. package/guides/connecting-agents.md +6 -0
  37. package/guides/safety-and-trust.md +1 -0
  38. package/package.json +1 -1
  39. package/templates/node/package.json +1 -1
@@ -6,16 +6,16 @@
6
6
  and package maintenance. This guide owns identity, membership, and downstream
7
7
  credential contracts; use [deployment setup](./deploying.md) for runnable steps.
8
8
 
9
- | Task | Sections |
10
- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
11
- | Connect a machine or migrate an old client | [Agent tokens](#agent-tokens-and-upgrading-from-earlier-tokens), [agent endpoints](#agent-endpoints), [static bearer migration](#migrating-static-bearer-clients) |
12
- | Configure caller visibility, read-only endpoints, or auth ownership | [Principals and operators](#principals-visibility-and-operators), [pools](#pools), [shared and personal auth](#shared-and-personal-auth) |
13
- | Lay out an agent's pools | [Organizing an agent's pools](#organizing-an-agents-pools) |
14
- | Set up human sign-in | [Access on Workers](#cloudflare-access-on-workers), [Clerk tokens and sessions](#clerk-oauth-tokens-and-operator-sessions), [Clerk Organizations](#one-clerk-instance-one-organization-per-deployment), [management](#human-authentication-management) |
15
- | Enable credential or connection controls | [Management permissions](#management-permissions), [start/restart/disconnect](#starting-restarting-and-disconnecting) |
16
- | Diagnose consent or token refresh | [State at rest](#downstream-oauth-state-at-rest), [callbacks](#consents-and-callbacks), [refresh failures](#refresh-failures), [safe errors](#what-a-servers-errors-may-say) |
17
- | Configure a downstream OAuth client or HTTP handler | [Client registration](#downstream-oauth-client-registration), [api() OAuth](#downstream-oauth-on-api), [request-local bearer tokens](#request-local-downstream-bearer-tokens), [Google hosted MCP](#google-service-account-tokens-and-hosted-mcp) |
18
- | Review browser or downstream URL boundaries | [Origins](#origins), [advertised URLs](#urls-a-downstream-advertises), [one server per grant](#one-server-per-grant) |
9
+ | Task | Sections |
10
+ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | Connect a machine or migrate an old client | [Agent tokens](#agent-tokens-and-upgrading-from-earlier-tokens), [agent endpoints](#agent-endpoints), [static bearer migration](#migrating-static-bearer-clients) |
12
+ | Configure caller visibility, read-only endpoints, or auth ownership | [Principals and operators](#principals-visibility-and-operators), [pools](#pools), [shared and personal auth](#shared-and-personal-auth) |
13
+ | Lay out an agent's pools | [Organizing an agent's pools](#organizing-an-agents-pools) |
14
+ | Set up human sign-in | [Access on Workers](#cloudflare-access-on-workers), [Clerk tokens and sessions](#clerk-oauth-tokens-and-operator-sessions), [Clerk Organizations](#one-clerk-instance-one-organization-per-deployment), [management](#human-authentication-management) |
15
+ | Enable credential or connection controls | [Management permissions](#management-permissions), [start/restart/disconnect](#starting-restarting-and-disconnecting) |
16
+ | Diagnose consent or token refresh | [State at rest](#downstream-oauth-state-at-rest), [callbacks](#consents-and-callbacks), [refresh failures](#refresh-failures), [safe errors](#what-a-servers-errors-may-say) |
17
+ | Configure a downstream OAuth client or HTTP handler | [Client registration](#downstream-oauth-client-registration), [api() OAuth](#downstream-oauth-on-api), [request-local bearer tokens](#request-local-downstream-bearer-tokens), [principal forwarding](#forwarding-the-admitted-principal), [Google hosted MCP](#google-service-account-tokens-and-hosted-mcp) |
18
+ | Review browser or downstream URL boundaries | [Origins](#origins), [advertised URLs](#urls-a-downstream-advertises), [one server per grant](#one-server-per-grant) |
19
19
 
20
20
  [Credential recognition and admission](#credential-recognition-and-admission)
21
21
  defines refusal precedence.
@@ -1336,6 +1336,29 @@ healthy. Restart invalidates it even if the connection ends healthy. Continue
1336
1336
  trusts the stored registration's `redirect_uris`; after changing `publicUrl`,
1337
1337
  Restart registers for the current callback URL.
1338
1338
 
1339
+ For `remoteMcp()` OAuth, the owner's stored grant decides whether a connection
1340
+ is connected, not whether the downstream answers. Some servers answer
1341
+ `initialize` and `tools/list` without credentials and challenge only
1342
+ `tools/call`. The operator status, the UI's connected state, and a start agree
1343
+ with the gate a call passes:
1344
+
1345
+ | Stored grant | Status | Start (Connect, Continue, Restart, `authorize_connector`) |
1346
+ | ------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1347
+ | Disconnected | `auth_required`; the downstream is not asked | Replaces the epoch, then proceeds as for no tokens |
1348
+ | No tokens | Connects; `auth_required` even when the downstream answers | Connects first, so a 401's `resource_metadata` starts consent as before. A downstream that answers without credentials still gets consent, discovered from RFC 9728 metadata at the configured URL's well-known locations. The start answers `auth_required` with the consent URL. |
1349
+ | Tokens (any expiry) | Connects; `ok`, or `auth_required` when a 401 needs consent | Connects; `ok` without starting a flow when healthy |
1350
+
1351
+ A start without a challenge runs the same SDK flow as a 401 without
1352
+ `resource_metadata`. Issuer and resource checks, consent state, PKCE, and
1353
+ the CIMD, registration, and static-client order are unchanged, and nothing is
1354
+ sent to the MCP endpoint. A call never starts consent. A call's 401 that names
1355
+ per-tool metadata, such as `/.well-known/oauth-protected-resource/<tool>`,
1356
+ renews through it when the grant holds no discovery. That metadata must name
1357
+ the configured resource, as any other must. When renewal is refused, the grant
1358
+ loses its tokens and the next start hands out consent. Restart (`force: true`)
1359
+ removes the tokens first, so it starts consent unless the downstream or
1360
+ discovery fails.
1361
+
1339
1362
  ### Consents and callbacks
1340
1363
 
1341
1364
  A start stores its flow record, then the grant's pointer to it. A reset that
@@ -2648,12 +2671,18 @@ For providers offering both, deployment config chooses; handlers check `ctx.oaut
2648
2671
 
2649
2672
  `remoteMcp("<id>", { auth: { type: "request", token: async (ctx) => … } })` resolves
2650
2673
  its token before connecting and before reusing a client. The callback receives
2651
- the operation's cancellation context, never an agent-selected credential owner.
2674
+ the operation's cancellation context only. It never receives an agent-selected
2675
+ credential owner, and without the opt-in below it never receives the caller.
2652
2676
  Only shared ownership is accepted. Tokens remain inside the request's hardened
2653
2677
  transport, are not stored or described, and a changed token replaces the client.
2654
2678
  This mode refuses cleartext non-loopback origins. Optional `auth.headers` carry
2655
2679
  static protocol/catalog controls and cannot set Authorization.
2656
2680
 
2681
+ Return the same token for the same caller while it remains valid, and cache a
2682
+ minted token until shortly before it expires. A value that changes on every
2683
+ resolution replaces the client each time; a change that lands while a call
2684
+ connects fails that call with a retryable error.
2685
+
2657
2686
  A resolver may expose `token.invalidate(rejectedToken, ctx): Promise<void>` to
2658
2687
  remove a cached token after a downstream HTTP 401. The hook receives the exact
2659
2688
  token sent by that transport, so it must leave a newer cached token intact.
@@ -2672,6 +2701,95 @@ is retained across requests, and rejected writes are never replayed. An omitted
2672
2701
  `describe()` includes neither the key nor the minted tokens. The maintained
2673
2702
  provider skill explains App permissions and installation prerequisites.
2674
2703
 
2704
+ ### Forwarding the admitted principal
2705
+
2706
+ Set `auth.principal` instead of `auth.token` to let the resolver act for the
2707
+ caller inbound auth admitted. Use it only for a downstream the deployment
2708
+ controls and that already trusts this deployment, so the downstream needs no
2709
+ per-person OAuth:
2710
+
2711
+ ```ts
2712
+ auth: {
2713
+ type: "request",
2714
+ principal: {
2715
+ accept: "human",
2716
+ token: async (ctx, { principal }) => await notesAssertion(principal),
2717
+ },
2718
+ },
2719
+ ```
2720
+
2721
+ - `principal` is `{ kind: "human", namespace, id }`: the admitted person's
2722
+ stable reference, the same `{ namespace, id }` a pool member resolves to.
2723
+ Inbound auth admits no verified email or display name into it; map the id in
2724
+ deployment code or in the downstream when one is needed.
2725
+ - `accept: "human"` admits only people. Agents, anonymous callers,
2726
+ principal-less machine credentials and contexts no request admitted are
2727
+ refused with `auth_required` before the resolver runs. Agents still see the
2728
+ connector, and their calls fail. No identity is ever substituted.
2729
+ - `accept: "human-or-agent"` also admits declared agents as
2730
+ `{ kind: "agent", name, pool }`, the pool being the one the agent arrived on.
2731
+ An agent is never presented as a person, its pool's owner included.
2732
+ - The caller comes only from the authorization that admitted the request.
2733
+ Tool arguments, program code and request headers cannot choose or change it.
2734
+ - The principal joins the client's identity. A different principal never
2735
+ reuses a client, connection or connect in flight, even when the resolver
2736
+ returns the same token. `token.invalidate(rejected, ctx, { principal })`
2737
+ receives the principal the rejected token was minted for.
2738
+ - Catalog listing needs an admitted caller like any call, and its cache stays
2739
+ partitioned per caller. Operator status and catalog checks carry no caller,
2740
+ so they report `auth_required` without connecting.
2741
+ - The connector stays shared. Connecta keeps no per-principal credential state
2742
+ and never stores, logs or describes the principal or the token. `describe()`
2743
+ reports `mode: "request"` as for any request resolver.
2744
+
2745
+ #### Over a Workers service binding
2746
+
2747
+ Pair principal forwarding with the optional `fetch` to reach a sibling Worker
2748
+ through a service binding. Then neither Cloudflare Access, the public internet
2749
+ nor a service token lies between the two Workers:
2750
+
2751
+ ```ts
2752
+ const notes = remoteMcp("notes", {
2753
+ url: "https://notes.example.com/mcp",
2754
+ fetch: (input, init) => env.NOTES.fetch(input, init),
2755
+ auth: {
2756
+ type: "request",
2757
+ principal: {
2758
+ accept: "human",
2759
+ token: async (_ctx, { principal }) => await notesAssertion(principal, env.NOTES_ASSERTION_KEY),
2760
+ },
2761
+ },
2762
+ });
2763
+ ```
2764
+
2765
+ `notesAssertion` is deployment code. It signs a short-lived assertion naming
2766
+ `principal.namespace` and `principal.id` with a key both Workers hold, and
2767
+ caches it per principal until shortly before it expires. Over a binding, the
2768
+ token may instead be an unsigned principal reference, provided the downstream
2769
+ reads it only where the trust rule below holds.
2770
+
2771
+ The downstream must accept a forwarded identity only through its binding
2772
+ entry point or after verifying the signature. Bind to a named entrypoint, such
2773
+ as `{ "binding": "NOTES", "service": "notes", "entrypoint": "Connecta" }` in
2774
+ the deployment's Wrangler `services`, and read the identity only in that
2775
+ entrypoint. Never accept it on a public route: anyone who can reach that route
2776
+ could name any person. A signature verified with a deployment-held key is also
2777
+ safe on a public route.
2778
+
2779
+ `fetch` carries every MCP request, including handshakes, catalog reads, calls,
2780
+ writes and session close. Global fetch is never used for that connector. The
2781
+ configured `url` stays the logical origin: its HTTPS rule, cleartext refusal,
2782
+ redirect policy and same-origin redirect checks apply unchanged. A request for
2783
+ any other origin is refused before it reaches `fetch`. Each request, writes
2784
+ included, is handed to `fetch` once with `redirect: "manual"`, and a dispatched
2785
+ write is never sent again, so `fetch` must not retry or follow redirects. On
2786
+ Node, such writes do not need the owned transport. `fetch` cannot be combined
2787
+ with OAuth auth, because discovery, registration and token requests go to
2788
+ authorization servers the downstream names.
2789
+
2790
+ Contributor evidence: `test/remote-mcp-principal-forwarding.test.ts` runs on
2791
+ Node and, through a real service binding, on workerd.
2792
+
2675
2793
  ### Google service-account tokens and hosted MCP
2676
2794
 
2677
2795
  Import `googleServiceAccountToken` from `@zackbart/connecta/google` to connect a
@@ -65,9 +65,15 @@ createConnecta({ executor: workerExecutor({ loader: env.LOADER }), readOnly: fal
65
65
  ```
66
66
 
67
67
  The adapter uses only `loader` and an optional `timeout`. Its admission defaults
68
- are two active runs, eight queued runs, and a five-second queue deadline, settable
69
- through `admission`. Lease release disposes RPC and loader handles independently
70
- of guest settlement. `bindings`, `modules`, or `globalOutbound` violate `P2`.
68
+ are two active runs, eight queued runs, a 60-second queue deadline (the default
69
+ guest deadline) and a one-second `retryAfterMs` hint. `admission` replaces all
70
+ four: `{ concurrency, maxQueueSize, queueTimeoutMs, retryAfterMs? }`, where an
71
+ omitted `retryAfterMs` equals `queueTimeoutMs`. The pool belongs to the executor
72
+ instance, so a Worker that builds one per isolate bounds each isolate, not the
73
+ deployment or a caller: concurrent requests that land on one isolate share its
74
+ slots, and requests on other isolates do not count. Lease release disposes RPC
75
+ and loader handles independently of guest settlement. `bindings`, `modules`, or
76
+ `globalOutbound` violate `P2`.
71
77
  The [Worker example](../examples/worker/README.md#code-mode) has the full setup.
72
78
 
73
79
  Keep the Worker's optional peer. Unbranded executors throw, including bundled upstream copies.
@@ -981,7 +987,21 @@ payload is refused before parsing and names no address.
981
987
  plus a bounded queue with a wait timeout. Overload is a retryable
982
988
  `executor_overloaded` carrying `retryAfterMs`; cancellation and shutdown are
983
989
  terminal. Admission happens _before_ any catalog or provider is built, so a
984
- queued request holds no state.
990
+ queued request holds no state. A full queue refuses at once with
991
+ `Executor queue is full.`; a queued run that outlives `queueTimeoutMs` is
992
+ refused with `Executor admission timed out after <n>ms.`. Either arrives as an
993
+ error result whose `structuredContent.error` is
994
+ `{ code: "executor_overloaded", message, retryable: true, retryAfterMs }`, with
995
+ `hostCalls` all zero; the program never started, so retrying it after
996
+ `retryAfterMs` sends nothing twice. Whichever request releases a slot, the
997
+ queued run resumes, builds its providers, makes its host calls and holds its
998
+ sandbox handles in its own request; the releasing request's result is
999
+ unaffected. A slot handed to a queued run is heard a turn after it is granted.
1000
+ When the executor closes in that gap, the slot is given back and the run is
1001
+ refused with `executor_closed`, so nothing granted before `close()` starts after
1002
+ it. A grant that beat the caller's abort stays the caller's, which releases it
1003
+ on arrival. A queued run also holds its MCP request permit
1004
+ ([request admission](./architecture.md#admission-in-two-places)) while it waits.
985
1005
 
986
1006
  **L8.** Result, log, host-call, and emission budgets are deployment configuration.
987
1007
  A call may request `timeoutMs` just as a direct call does; the guest deadline is
@@ -1287,7 +1307,7 @@ rejection, and branded adapter acceptance across module copies.
1287
1307
  | `L1`, `L2` | `test/guest-api-contract.test.ts` (in-flight call fails `cancelled`), `test/execute.test.ts` (cancels outstanding host calls, discovery included, and refuses discovery after the run; a cancelled wedged executor, or one whose `acquire()` ignores the signal, returns promptly and releases its lease) |
1288
1308
  | `L3`, `X1` | `test/guest-api-contract.test.ts` (short-deadline executors), `test/execute.test.ts` (the watchdog ends a never-settling executor, frees the default pool, spares a slow run, and falls back from an unusable value) |
1289
1309
  | `L4`, `L8` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (shared discovery/call budgets and terminal catch-and-continue loops and queued-write cancellation on both executors), `test/worker-budget-response.node.test.ts` (native handle disposal and admission recovery across completed HTTP responses) |
1290
- | `L5`, `L7`, `X2` | `test/quickjs-executor.node.test.ts` (CPU, heap), `test/execute.test.ts` and `test/executor-admission.test.ts` (bounded admission and queue) |
1310
+ | `L5`, `L7`, `X2` | `test/quickjs-executor.node.test.ts` (CPU, heap), `test/execute.test.ts` and `test/executor-admission.test.ts` (bounded admission and queue), `test/executor-requests.test.ts` (queued handoffs and refusal shape across separate workerd requests), `test/request-context.node.test.ts` (queued and QuickJS host-call request context) |
1291
1311
  | `L6`, `X10` | `test/guest-api-contract-quickjs.node.test.ts` and `test/guest-api-contract.test.ts` (whole oversized reads and one-write pageable handles), `test/sqlite-storage.node.test.ts` and `test/kv-to-d1.test.ts` (SQL stash and native D1 fault probes), `test/quickjs-executor.node.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message), `test/quickjs-child-stderr.node.test.ts` (outer reply serialization failure settles the call) |
1292
1312
  | `V1`–`V4` | `test/guest-api-contract.test.ts` (dispatched calls, every refusal class including an address no connector owns, the friction each derives, no event for the execution itself), `test/activity.test.ts` (the shared code → friction table, the identity clamp, the one-attempt floor), `test/browser/operator-ui.spec.ts` (identity changes), `test/sql-storage-contract.ts`, run by `test/sqlite-storage.node.test.ts` (historical activity rows still round-trip) |
1293
1313
  | `V5`, `W9` | `test/program-writes.test.ts` (unawaited program drain, failed-program accounting), `test/identity-write-journey.node.test.ts` (committed ambiguity, reconciliation and physical writes), `test/invocation-pipeline.test.ts` (the gate after validation, an unrecorded refusal) |
@@ -163,15 +163,28 @@ also supports OAuth, static headers, and request-local token resolution. Use
163
163
  [client registration](./auth.md#downstream-oauth-client-registration), and
164
164
  [request-local tokens](./auth.md#request-local-downstream-bearer-tokens)
165
165
  for those cases. Secrets belong in deployment secret storage or the vault,
166
- not source literals.
166
+ not source literals. For a first-party downstream that already trusts this
167
+ deployment, request auth can
168
+ [forward the admitted principal](./auth.md#forwarding-the-admitted-principal)
169
+ instead of asking each person to authorize it.
170
+
171
+ Optional `fetch` sends every MCP request for one connector through a
172
+ deployment-supplied function instead of global fetch, such as a Workers
173
+ service binding: `fetch: (input, init) => env.NOTES.fetch(input, init)`. `url`
174
+ still names the logical origin, and its HTTPS, redirect and same-origin rules
175
+ apply. `fetch` receives each request once with `redirect: "manual"` and must
176
+ not retry. It cannot be combined with OAuth auth. See
177
+ [over a Workers service binding](./auth.md#over-a-workers-service-binding).
167
178
 
168
179
  Use optional top-level `headers` for non-secret resource context:
169
180
  `remoteMcp("crm", { url: "https://crm.example/mcp", headers: {
170
181
  "X-MCP-Toolsets": "tickets,notes" }, readOnly: true })`. These headers apply to
171
182
  MCP handshake, catalog and call requests at the exact resource URL path, never OAuth
172
183
  metadata, token or registration requests. Values are included in cache and
173
- negotiation identities; values of at least eight characters are redacted if echoed. Configuration descriptions
174
- show only names. Never put credentials here. Use `auth` for secrets. Extra
184
+ negotiation identities. They are never treated as sent secrets: program and
185
+ direct-call arguments equal to a header value reach the downstream unchanged on
186
+ every call, and results, catalogs and errors that echo a value return it as
187
+ sent. Configuration descriptions show only names. Never put credentials here. Use `auth` for secrets. Extra
175
188
  headers cannot be combined with `auth.type: "headers"`, collide with an
176
189
  auth-owned name, or set HTTP transport/session headers
177
190
  (`Accept`, `Cookie`, `Host`, `Content-*`, hop-by-hop headers, `Last-Event-ID`,
@@ -205,7 +218,8 @@ optional and composes with endpoint policy.
205
218
  On Node, serve with `listen` from `@zackbart/connecta/node`: it installs the
206
219
  owned transport that prevents native fetch or a host-configured dispatcher
207
220
  from silently resending a remote MCP write. Without that transport, Node
208
- refuses remote writes before dispatch. Downstream OAuth token, registration and
221
+ refuses remote writes before dispatch. A connector with its own `fetch` hands
222
+ writes to that function instead, which owns their single attempt. Downstream OAuth token, registration and
209
223
  revocation POSTs also require the owned sender. Read calls and OAuth discovery
210
224
  GETs keep native fetch. Workers
211
225
  use their native fetch with manual redirects; transport failures after
@@ -43,6 +43,25 @@ clock, UTC, en-US, locally hosted fonts, disabled animations, hidden carets,
43
43
  and a 1280 × 900 viewport. Narrow fixtures use 390 × 844. Every test waits for
44
44
  its state and fonts before capturing.
45
45
 
46
+ ## Loading and recovery probes
47
+
48
+ `test/operator-contract-server.test.ts` counts physical legacy MCP initialize,
49
+ initialized, SSE GET and tools/list requests: cached admission and summary perform
50
+ none, then one detail per connector performs one session and complete listing.
51
+ The suite also checks default v2 compatibility, the unchanged member projection,
52
+ blocked custom connectors and bounded essential token-slot failure.
53
+ `test/ui.test.ts` checks complete detail schemas and independent sibling scope cleanup.
54
+
55
+ `test/oauth-stored-observation.test.ts` covers absent, disconnected, tokenless,
56
+ sealed/unreadable and issuer-mismatched grants, owner isolation, both built-in
57
+ connector paths and custom connectors. Storage spies require zero auth mutations,
58
+ including migration and resealing. `test/operator-store.test.ts` counts scoped
59
+ refresh/focus/mutation/poll requests, coalescing and bounded retries, and verifies
60
+ stale admission blocks privileged reads, connection writes and token actions while
61
+ retaining the snapshot. Late reads, role/identity changes and concurrent polls
62
+ exercise publication fences. Browser recovery and flow suites use real routes to
63
+ check a usable shell with a stalled connector, OAuth return and scoped Refresh.
64
+
46
65
  ## Page and state matrix
47
66
 
48
67
  Each row runs empty, loading, error, populated, and restricted-viewer fixtures
@@ -391,6 +391,21 @@ activity and access-token routes answer 404, and owners are `null`. Every respon
391
391
  allowed. One shape serves both roles; a field a role does not receive is
392
392
  `null` or empty, never omitted.
393
393
 
394
+ The browser uses `GET /ui/api/config?live=cached` for admission. For maintainers,
395
+ this mode performs no downstream status or catalog probes. `live.connectors`
396
+ contains only known observations, or `[]`; absence means unobserved, never healthy
397
+ or failed. The default URL and published v2 type remain unchanged. Members receive
398
+ their existing projection in either mode. Cached metadata reads have a 500 ms
399
+ budget each and the entire metadata build has a five-second budget, below the
400
+ browser's 15-second admission deadline. Essential token-slot failure still returns 503. Optional reads degrade conservatively. No downstream session spans requests.
401
+
402
+ Cached matrix counts use credential vault metadata or a built-in OAuth stored-auth
403
+ observation in each owner's partition. This boolean observation opens sealed grants
404
+ and checks token presence, disconnect state and issuer binding, without refreshing,
405
+ authorizing, migrating or writing auth. Unreadable grants and custom connectors
406
+ without this capability contribute an unconfirmed count of zero. For example,
407
+ `notes_alice` reporting zero connected people does not prove that nobody connected.
408
+
394
409
  The maintainer view carries:
395
410
 
396
411
  - `config`, the construction-time `describeConfig()` snapshot, including
@@ -507,6 +522,15 @@ exception: clearing a stored value that cannot be read is accepted although
507
522
  `manage` lists only `save` in that state. Public HTML shells and static assets carry no catalog data.
508
523
  Access-token management also requires an interactive session.
509
524
 
525
+ `GET /ui/connectors/:id` returns the existing auth controls plus an optional typed
526
+ `overlay`, all from the same status/catalog observation. The overlay contains
527
+ complete classified tools with input/output schemas, registration path, catalog
528
+ age, and activity-derived last-call facts under the reader's disclosure policy.
529
+ A failed or timed-out connector releases its own scope; siblings finish independently.
530
+ The browser publishes the detail and overlay atomically. Config observations never
531
+ overwrite a completed detail row. Each connector opens at most one live observation
532
+ per page load; the summary `/ui/data` performs no downstream probes.
533
+
510
534
  `GET /ui/api/config?connector=<id>` narrows the same contract to one connector
511
535
  before status and catalog probes. For a maintainer, config, provenance, live
512
536
  rows and matrix cells contain only that connector's facts; for a member,
@@ -545,52 +569,39 @@ no duplicate standalone server modules. The shared contract owns the unchanged
545
569
  `OperatorPage` union exported from `@zackbart/connecta/ui`. An Access-only shell selects
546
570
  Access even when the HTML request has no trusted context; only authenticated
547
571
  API responses establish the session. A gated Access session offers Retry.
548
- Focus, visibility, and timer-driven recovery share one admission-read schedule
549
- with at least two seconds between passive attempts. An admission read takes
550
- the contract first and, for a maintainer, then `/ui/data` and the connector
551
- details, which reuse that contract rather than read it again. An in-flight
552
- passive attempt coalesces returns until its admission read finishes, so slow
553
- successful reads can publish. Each admission read shares one 15-second
554
- deadline, including token acquisition, fetches and response bodies. This is an admission deadline, not a request
555
- lifetime limit: it releases the recovery guard and exposes Retry without aborting
556
- the pending read or invalidating its identity/revision fence. A late healthy
557
- response still publishes if that cycle is current. Passive timers and returns
558
- coalesce onto the pending cycle, including after its deadline; they never start
559
- duplicate summary, token, config, detail or body reads. The passive retry schedule
560
- stays bounded and does not reset its budget on a deadline. Retry remains available
561
- during recovery configuration loading and supersedes and aborts the old cycle;
562
- identity changes, navigation and connector-page unmount also cancel it. Navigation
563
- that cancels pending data schedules a bounded reread for the destination. A scoped OAuth poll publishes only its connector's detail and live row. It does
564
- not supersede a pending full admission or catalog/auth cycle, release its
565
- recovery guard, or clear its deadline. That full cycle still publishes other
566
- connectors as they settle and decides recovery, retaining the newer polled row
567
- even when its summary was pending or its deadline exposed Retry. Only a newer
568
- full cycle, mutation, identity change, or sign-out fences older work. Owed
569
- publication neither spends nor resets the failed-read retry budget and adds no
570
- timer. Failed reads retry after
571
- 2, 2, 4, and 8 seconds. Refusals preserve the throttle and retry budget. After
572
- those four automatic retries, a focus return can request one more read after
573
- the eight-second backoff, without replenishing the automatic budget. Retry
574
- resets the budget with a 150 ms click debounce. Successful reads and explicit
575
- identity transitions also reset recovery. Identity changes
576
- cancel pending retries and fence out responses from the previous identity.
577
- Queued focus callbacks carry the same identity fence. Explicit sign-out
578
- suppresses passive reads until an intentional sign-in or a new Clerk session.
579
-
580
- Catalog and connector auth details share one revisioned refresh cycle. Each
581
- completed connector publishes its catalog facts and auth controls together,
582
- while a slow provider leaves other rows usable.
583
- Catalog refresh, connector refresh, and mutation follow-up reads update both
584
- consumers together. A later refresh or mutation invalidates an older cycle's publication fence
585
- without aborting its read, and a failed contract read clears its old snapshot;
586
- the next refresh then reads the contract before any connector detail. After an explicit OAuth
587
- start, visible tabs poll only that connector's read-only catalog and auth facts
588
- every two seconds for up to 30 seconds. The scoped result replaces only that
589
- connector's matching live row and auth controls. Success, a failed read or status,
590
- disconnect, navigation away from the connector, unmount, or identity reset ends
591
- polling and aborts its in-flight reads. The deadline also aborts a stalled read.
592
- A later focus return can request another status read. Recovery never starts OAuth
593
- or replays a mutation.
572
+ Focus, visibility and timer-driven admission recovery share a schedule with at
573
+ least two seconds between passive attempts. Admission reads the cached contract
574
+ first, then `/ui/data` for maintainers. Token acquisition, fetches and response
575
+ bodies share a 15-second deadline. Once admission succeeds, connector details
576
+ have independent deadlines and cannot withdraw deployment-wide admission.
577
+
578
+ A passive admission deadline retains the last maintainer snapshot with a stale
579
+ notice. Admission validity is separate from snapshot presence: privileged reads,
580
+ connection writes and token actions require valid admission. A scoped connector
581
+ result cannot restore it. Queued reads withheld locally after admission expires
582
+ are discarded without clearing the snapshot or showing sign-in. A late full
583
+ admission may publish behind its original identity and revision fences. HTTP
584
+ 401/403 refusal, identity change and sign-out clear immediately;
585
+ member failures still remove their actionable projection. Retry aborts the old
586
+ admission cycle; navigation and identity transitions cancel its reads. Passive
587
+ attempts coalesce even after the deadline. Automatic admission retries remain
588
+ bounded at 2, 2, 4 and 8 seconds; a later focus return can try again after backoff.
589
+
590
+ Connector Refresh, focus recovery and mutation follow-ups read only affected
591
+ connectors. Focus probes unhealthy rows, never healthy siblings. All detail reads
592
+ share a four-request limit and coalesce per connector. Failures retry only their
593
+ connector with the same bounded backoff; recovery never replays a mutation.
594
+ Mutations cancel affected reads and advance their publication fence both at
595
+ dispatch and on settlement, including failure. Follow-ups require an observation
596
+ started after settlement, so a Refresh or retry started during the write cannot
597
+ supply its result. Newer publications also fence older results per connector,
598
+ while identity and full refresh revisions fence the complete view. An explicit
599
+ OAuth start polls only that connector every two seconds for up to 30 seconds.
600
+ Concurrent authorizations retain independent cancellation and publication. Success,
601
+ failed reads, disconnect, navigation, unmount and identity reset stop the matching
602
+ poll. Recovery never starts authorization. A scoped result cannot confirm identity,
603
+ role, token slots or any other deployment-wide admission fact.
604
+
594
605
  Unreadable credential metadata uses `configured: null`, including named fields,
595
606
  and renders as unavailable with a retry prompt. It does not claim that the slot
596
607
  is empty. Overview, Endpoints, Connectors, connector
@@ -547,7 +547,11 @@ LOADER: Cloudflare.WorkerLoader(),
547
547
  `@zackbart/connecta/worker`. The adapter constructs the upstream
548
548
  `DynamicWorkerExecutor` with only the loader and a deadline, and disposes each
549
549
  run's loader and RPC handles when its lease ends, even if the guest has not
550
- settled. It serves the six-tool surface. Do not add `bindings`, `modules`, or
550
+ settled. It serves the six-tool surface. Its admission pool is per isolate:
551
+ two runs, eight queued for up to 60 seconds, then a retryable
552
+ `executor_overloaded`. Pass `admission` to `workerExecutor()` to change it; see
553
+ [code mode](../../documentation/code-mode.md#deploy-time-capability). Do not
554
+ add `bindings`, `modules`, or
551
555
  `globalOutbound`; they grant guest code ambient authority. A copied deployment
552
556
  owns the package install — see
553
557
  [copied into its own repository](#copied-into-its-own-repository).
@@ -146,6 +146,12 @@ owner whose account the agent will use. See
146
146
  [Workspace pool owners](../documentation/auth.md#pool-owners-for-google-workspace)
147
147
  before connecting an agent to someone's account.
148
148
 
149
+ A connection to the organization's own service can instead act as whoever
150
+ signed in, by forwarding that person's identity. Agents cannot use such a
151
+ connection unless the operator also allows them, and then they appear to the
152
+ service as themselves, never as a person. See
153
+ [forwarding the admitted principal](../documentation/auth.md#forwarding-the-admitted-principal).
154
+
149
155
  An assistant serving several people must use a pool appropriate for everyone
150
156
  in the conversation. Its app chooses that pool; the model does not choose
151
157
  its own access level. Keep private results out of broader conversations.
@@ -141,6 +141,7 @@ own availability, project/environment choices, and revocation details.
141
141
  | Enable the credential vault | Encrypt stored credentials and OAuth tokens before connecting accounts. |
142
142
  | Pool membership and tools | Each member can use every action that pool grants. |
143
143
  | Service permissions | A shared credential uses the same account for every member. |
144
+ | Forwarded identity | A service that trusts a forwarded person must accept it only from this deployment. |
144
145
  | Approval policy | A writable pool can run unattended; the Eve helper defaults to that behavior. |
145
146
  | Tool classification | Labels must match the service's actual behavior. |
146
147
  | Deployment and secret handling | Enable the credential vault; protect storage, encryption keys, backups, ingress, and the agent host. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.36.2",
3
+ "version": "0.37.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "One MCP to rule them all — a single MCP endpoint aggregating many downstream connectors behind a code-first surface of six meta-tools.",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@clerk/backend": "^3.12.0",
21
- "@zackbart/connecta": "0.36.2",
21
+ "@zackbart/connecta": "0.37.0",
22
22
  "quickjs-emscripten": "0.32.0",
23
23
  "undici": "6.29.1"
24
24
  },