@zackbart/connecta 0.16.1 → 0.17.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 (42) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/catalog-service.d.ts +4 -0
  3. package/dist/catalog-service.js +8 -1
  4. package/dist/catalog.js +114 -12
  5. package/dist/errors.d.ts +4 -6
  6. package/dist/execute.d.ts +5 -0
  7. package/dist/execute.js +229 -161
  8. package/dist/invocation.js +3 -1
  9. package/dist/meta-tools.d.ts +4 -0
  10. package/dist/meta-tools.js +46 -14
  11. package/dist/operator-ui/generated.d.ts +1 -1
  12. package/dist/operator-ui/generated.js +1 -1
  13. package/dist/operator-ui/model.d.ts +3 -1
  14. package/dist/providers/mixpanel.d.ts +3 -5
  15. package/dist/providers/mixpanel.js +73 -5
  16. package/dist/providers/stripe.d.ts +2 -2
  17. package/dist/providers/stripe.js +13 -11
  18. package/dist/registry.d.ts +32 -9
  19. package/dist/registry.js +217 -33
  20. package/dist/routes/mcp.js +6 -0
  21. package/dist/skills.d.ts +4 -0
  22. package/dist/skills.js +157 -18
  23. package/dist/types.d.ts +14 -2
  24. package/dist/ui.js +4 -1
  25. package/dist/version.d.ts +1 -1
  26. package/dist/version.js +1 -1
  27. package/documentation/architecture.md +7 -4
  28. package/documentation/code-mode.md +45 -53
  29. package/documentation/connector-guides.md +24 -19
  30. package/documentation/connectors.md +13 -1
  31. package/documentation/meta-tools.md +26 -17
  32. package/documentation/mixpanel.md +20 -0
  33. package/documentation/operations.md +21 -18
  34. package/documentation/operator-ui.md +12 -2
  35. package/documentation/provider-audit.md +3 -3
  36. package/documentation/provider-conventions.md +26 -13
  37. package/documentation/stripe.md +45 -14
  38. package/documentation/upgrading.md +28 -4
  39. package/ethos.md +3 -3
  40. package/examples/worker/README.md +4 -3
  41. package/package.json +1 -1
  42. package/templates/node/package.json +1 -1
@@ -40,13 +40,17 @@ hand-written connector. It is deployment-owned configuration like everything
40
40
  else here: an edit and a redeploy, never a runtime registration.
41
41
 
42
42
  `content` is returned byte for byte by `skills({ name: "connector:<id>" })`.
43
- `summary` is normalized and capped at 120 characters for discovery. Omit it and
44
- connecta derives the same bounded line the skills listing uses: the first
45
- meaningful body line, with frontmatter, fences, rules, comments, and table rows
46
- skipped, a heading used only when the guide has no body, and the connector's
47
- description as the last resort. A derived summary is usually worse than a
48
- written one it was written to open a document, not to answer "is this guide
49
- relevant to what I am about to do".
43
+ `summary` is normalized and must fit 120 characters. A longer configured value
44
+ refuses construction instead of silently changing the operator's words. Omit
45
+ it and connecta derives the same bounded summary the skills listing uses: the
46
+ first meaningful body paragraph, joined across Markdown's physical line wraps,
47
+ with frontmatter, fences, rules, comments, and tables skipped. When the
48
+ paragraph does not fit, connecta keeps a useful complete sentence when one
49
+ fits, then prefers a clause or word boundary before adding an ellipsis. A heading is used
50
+ only when the guide has no body, and the connector's description is the last
51
+ resort. A derived summary is usually worse than a written one — it was written
52
+ to open a document, not to answer "is this guide relevant to what I am about
53
+ to do".
50
54
 
51
55
  `connector:<id>` is the only address for a guide, and built-in skill names are
52
56
  bare identifiers, so a guide can never shadow or be shadowed by `usage`: a
@@ -160,15 +164,16 @@ schemas will never carry, because it cannot change them
160
164
  ## Tests that enforce this
161
165
 
162
166
  `test/meta-tools.test.ts` owns the guide behavior end to end: the skills
163
- listing carrying one entry per guided connector, summaries derived from the
164
- first meaningful line and falling back to the connector description when the
165
- guide is all markup, whitespace-only guides treated as no guide, content
166
- returned verbatim including surrounding padding, identical content in two
167
- deployments staying isolated, every miss erroring rather than falling back to
168
- the generic guide with an identically labelled skills list on each branch, the
169
- `guide` pointer in search output, and `guideRequired` appearing for
170
- connector-required conventions, approval-bound tools, and truncated schemas —
171
- and being absent from a search that asked for no schemas. `test/server.test.ts`
172
- owns the conditional half: it compares a guide-free deployment's four tool
173
- descriptions against a guided one's, and asserts the `usage` skill is
174
- byte-identical between them.
167
+ listing carrying one entry per guided connector, summaries joining a
168
+ hard-wrapped opening paragraph and shortening at readable boundaries,
169
+ configured summaries refusing construction past the bound, heading and
170
+ description fallbacks, markup skipping, whitespace-only guides treated as no
171
+ guide, content returned verbatim including surrounding padding, identical
172
+ content in two deployments staying isolated, every miss erroring rather than
173
+ falling back to the generic guide with an identically labelled skills list on
174
+ each branch, the `guide` pointer in search output, and `guideRequired`
175
+ appearing for connector-required conventions, approval-bound tools, and
176
+ truncated schemas and being absent from a search that asked for no schemas.
177
+ `test/server.test.ts` owns the conditional half: it compares a guide-free
178
+ deployment's four tool descriptions against a guided one's, and asserts the
179
+ `usage` skill is byte-identical between them.
@@ -252,6 +252,17 @@ the cursor ends, preserve schemas and annotations, and never cache or serve a
252
252
  partial walk. The fixed TTL is paired with a schema fingerprint so a changed
253
253
  catalog invalidates persisted results even within the time window.
254
254
 
255
+ Agent reads use a complete entry inside `staleCatalogSeconds` immediately and
256
+ defer the refresh that read already demanded. Every live refresh is
257
+ single-flight per connector in one runtime. A blocking operator or direct read
258
+ joins an agent-owned refresh and awaits it; an agent stale read joins an
259
+ operator-owned refresh without awaiting it. The first refresh owns the context
260
+ and deadline. A deferred first refresh owns a fresh scope and signal, then
261
+ closes that scope. Operator status and direct registry reads still await
262
+ freshness. The operator page reports whether the last agent read in this runtime
263
+ was fresh or stale; this payload-free timestamp is not persisted. No timer or
264
+ idle warmup originates downstream traffic.
265
+
255
266
  Tool calls must use the shared invocation path. That keeps direct calls, batch
256
267
  children, and code-mode host calls aligned on safety, retries, admission,
257
268
  timeouts, validation, result guards, and typed failures.
@@ -259,7 +270,8 @@ timeouts, validation, result guards, and typed failures.
259
270
  Connector usage guides are configuration too. `usageGuide` accepts the
260
271
  historical markdown string or `{ content, summary?, required? }`; the latter
261
272
  lets discovery explain what the guide covers without loading it. The summary
262
- is only a bounded routing hint. Mark a guide `required` only when no complete
273
+ is a 120-character routing hint; a longer configured value refuses construction
274
+ instead of being silently shortened. Mark a guide `required` only when no complete
263
275
  tool schema can describe correct use, such as a generic operation wrapper or a
264
276
  mandatory cross-tool sequence. Mutations and truncated compact schemas already
265
277
  produce automatic review requirements. Two deployments may reuse the same
@@ -89,14 +89,16 @@ Compact search is deliberately a routing view, not a second copy of connector
89
89
  documentation. Tool purposes are capped at 160 characters, connector
90
90
  descriptions and property prose are omitted, required input fields render
91
91
  before optional ones, and each input or output shape is capped at 1,024 UTF-8
92
- bytes. Within that unchanged total, each enum node may spend at most 256 UTF-8
93
- bytes. This lets about three near-cap enum nodes coexist while reserving the
94
- remaining quarter for surrounding syntax; the global fallback still applies
95
- when the complete shape exceeds 1,024 bytes. A large enum keeps the longest
96
- whole-value prefix that fits, then adds `unknown` and a comment with the exact
97
- omitted-value count. An empty enum renders as the valid `never` type. A capped
98
- object becomes a valid required-first shape with `unknown` types; other shapes
99
- become `unknown /* truncated */`. Either cap marks the match with
92
+ bytes. Within that unchanged total, each enum node and each constraint
93
+ annotation may spend at most 256 UTF-8 bytes. Numeric bounds, string length
94
+ bounds, patterns, and formats render beside their type. A constraint that does
95
+ not fit is dropped whole. If constraints push the full shape over 1,024 bytes,
96
+ search retries the shape without them. Compact describe keeps all declared
97
+ constraints. A large enum keeps the longest whole-value prefix that fits, then
98
+ adds `unknown` and a comment with the exact omitted-value count. An empty enum
99
+ renders as the valid `never` type. A capped object becomes a valid
100
+ required-first shape with `unknown` types; other shapes become
101
+ `unknown /* truncated */`. Any cap marks the match with
100
102
  `inputSchemaTruncated` or `outputSchemaTruncated`; repeat the search with
101
103
  `includeSchemas: "json"` or use the existing describe path when exact
102
104
  constraints matter. Small enums and both exact paths remain complete.
@@ -107,12 +109,13 @@ A connector may attach a deployment-owned guide as markdown, preserving the
107
109
  original `usageGuide: string` configuration, or as
108
110
  `{ content, summary?, required? }`. The structured form does not register a
109
111
  connector or create a shared runtime template. `content` remains the markdown
110
- returned verbatim by `skills`; `summary` is normalized and capped at 120
111
- characters for discovery. When it is absent, Connecta derives the same bounded
112
- fallback used by the skills listing: the first meaningful body line, with a
113
- heading used only when the guide has no body. `required: true` is reserved for generic
114
- API wrappers and cross-operation conventions a complete downstream schema
115
- cannot express.
112
+ returned verbatim by `skills`; `summary` is normalized and refuses construction
113
+ when it exceeds 120 characters. When it is absent, Connecta derives the same
114
+ bounded fallback used by the skills listing: the first meaningful body
115
+ paragraph, joined across physical Markdown line wraps and shortened at a
116
+ sentence, clause, or word boundary, with a heading used only when the guide has
117
+ no body. `required: true` is reserved for generic API wrappers and
118
+ cross-operation conventions a complete downstream schema cannot express.
116
119
 
117
120
  Search and describe results keep the existing `guide: "connector:<id>"`
118
121
  pointer and add `guideSummary`. A matching tool also carries
@@ -170,12 +173,18 @@ can shrink anything before it returns.
170
173
 
171
174
  `fields` keeps its historical flat `{ "<path>": value }` result when every
172
175
  requested dot-path resolves. Dot notation traverses objects; append `[]` to an
173
- array field before continuing, as in `results[].id`. An exact downstream
176
+ array field before continuing, as in `results[].id`. Empty arrays resolve to
177
+ empty arrays. An exact downstream
174
178
  `$connecta` field is always escaped under `data`. If any path misses—or that
175
179
  reserved name is selected—the result carries matches under `data` and reserves `$connecta` for a
176
180
  `type: "field_projection"` recovery record naming each `unmatchedFields`
177
- entry. When a miss matches a declared array path except for `[]`, the record
178
- also carries the traversal hint. The discriminator means downstream fields
181
+ entry. A path that resolves for only some array elements stays in `data` and
182
+ appears in `partialFields`; its unresolved positions serialize as `null`, while
183
+ the recovery record distinguishes them from genuine downstream nulls. A path
184
+ that misses every element appears in `unmatchedFields` and is omitted from
185
+ `data`. Both lists scale with requested paths, never with array length. When a
186
+ miss matches a declared array path except for `[]`, the record also carries the
187
+ traversal hint. The discriminator means downstream fields
179
188
  named `data`, `projection`,
180
189
  or `$connecta` remain ordinary values nested under `data`, never apparent
181
190
  metadata. A declared output schema contributes a bounded `availableFields`
@@ -50,6 +50,26 @@ password, not ordinary configuration. Mixpanel currently labels service-account
50
50
  MCP authentication beta. Prefer OAuth unless the deployment is intentionally
51
51
  headless.
52
52
 
53
+ ## Conditional input contracts
54
+
55
+ Mixpanel's hosted descriptions enforce three cross-field conditions that its
56
+ input schemas do not encode. Connecta preserves those schemas unchanged under
57
+ [P1](./provider-conventions.md#p1--normalize-by-adding-never-by-rewriting), so
58
+ the maintained guide carries the missing call guidance:
59
+
60
+ - `Get-Business-Context` requires `project_id` or `organization_id`.
61
+ - `Get-Property-Values` requires `properties` or the deprecated `property`
62
+ alias. Event property values also require `event`.
63
+ - `List-Properties` accepts `names` or `query`, never both.
64
+
65
+ A read-only live audit on 2026-08-13 confirmed all three refusals against the
66
+ US hosted endpoint. They are reported upstream as
67
+ [`mixpanel/mixpanel-headless#202`](https://github.com/mixpanel/mixpanel-headless/issues/202).
68
+ The vetted catalog records the same audit's schema digests for all 63 tools,
69
+ so a later schema correction or regression appears by tool name in the
70
+ maintainer drift check. The guide can then shrink when the downstream schema
71
+ becomes complete; Connecta does not absorb the defect permanently.
72
+
53
73
  The wrapper classifies the documented observational tools as reads and the
54
74
  documented create, update, edit, merge, dismiss, duplicate, and delete tools as
55
75
  writes. An unfamiliar tool the downstream leaves unannotated fails closed onto
@@ -8,15 +8,18 @@ needs most.
8
8
 
9
9
  `createConnecta(config)` returns `{ fetch, registry, close }`. `fetch` takes
10
10
  the Workers `(request, env, ctx)` signature; passing `ctx` through is what lets
11
- connecta hand deferred work — best-effort activity writes — to `ctx.waitUntil`
12
- instead of losing it when the response returns.
11
+ connecta hand deferred work to `ctx.waitUntil` instead of losing it when the
12
+ response returns. That work is best-effort activity writes and the bounded
13
+ refresh an agent catalog read already demanded while it served a complete stale
14
+ entry. Node's adapter tracks the same promises and drains them on shutdown.
13
15
 
14
16
  An `executor` is required. A deployment without one throws at construction
15
17
  rather than serving a smaller surface
16
18
  ([#273](https://github.com/zackbart/connecta/issues/273)): Node uses
17
19
  `quickJsExecutor()` from `@zackbart/connecta/quickjs`, Workers use
18
20
  `new DynamicWorkerExecutor({ loader: env.LOADER })` from
19
- `@cloudflare/codemode`.
21
+ `@cloudflare/codemode`. The Worker executor must stay loader-only: `bindings`,
22
+ `modules`, and `globalOutbound` grant ambient guest authority.
20
23
 
21
24
  Both executor packages are optional peers: they never install with connecta,
22
25
  and a deployment installs the one its runtime needs. The manifest publishes the
@@ -84,7 +87,7 @@ optional.
84
87
  | `discovery.concurrency?` | 4 | connector catalogs/status probes in flight at once |
85
88
  | `discovery.catalogTtlSeconds?` | 300 | fresh TTL for cached tool lists |
86
89
  | `discovery.persistCatalog?` | true | persist serializable catalogs as a manifest plus revision-addressed chunks |
87
- | `discovery.staleCatalogSeconds?` | 3600 | how long an expired catalog stays usable as a failure fallback |
90
+ | `discovery.staleCatalogSeconds?` | 3600 | how long a complete expired catalog stays usable for agent SWR and as a refresh-failure fallback |
88
91
  | `discovery.probeTimeoutMs?` | 30_000 | per-connector deadline for catalog fan-out; a timed-out connector degrades alone. Not a tool-call deadline |
89
92
  | `calls.defaultTimeoutMs?` | **unset (opt-in)** | deadline for calls that pass no `timeoutMs`. Bounds one attempt, so retries can still extend total duration |
90
93
  | `calls.maxResultBytes?` | 50_000 | inline result cap before truncation and `get_result` paging; a connector may override it. Invalid values warn and fall back |
@@ -221,7 +224,7 @@ in.
221
224
  | `catalog.test.ts` | lexical ranking and the compact schema renderer — `const`, `allOf` beside siblings, `$ref`, the depth limit, per-schema caching, and 2020-12 keyword compatibility |
222
225
  | `clerk.test.ts` | protected-resource metadata, the browser sign-in config, OAuth and session tokens, cached best-effort activity labels with their caps, the hand-applied `azp` rejection, and the `allowedDomains` allowlist including every lookalike that must not be repaired into a match |
223
226
  | `cloudflare-provider.test.ts` | `cloudflare()` construction, tool surface, request building, projections, typed failures, and credential test |
224
- | `cloudflare-registry.test.ts` | the same provider inside a real deployment: discovery, addressing, and admission through the registry |
227
+ | `cloudflare-registry.test.ts` | the same provider inside a real deployment: discovery including compact page bounds, addressing, and admission through the registry |
225
228
  | `code-first-surface.test.ts` | the seven-tool surface itself — an executor required and both runtime configurations named, every removed option and removed top-level tool refused, and `connecta.ui` findable before an agent chooses catalog search |
226
229
  | `codemode-compat.test.ts` | the `Executor` seam staying structurally compatible with `@cloudflare/codemode`'s `DynamicWorkerExecutor`, enforced by `tsc` |
227
230
  | `config.test.ts` | the grouped `ConnectaConfig` boundary — each group forwarding to its internals, malformed admission bounds failing construction, and one complete migration error for legacy own-properties |
@@ -229,28 +232,28 @@ in.
229
232
  | `d1-activity-example.test.ts` | the Worker example's deployment-owned D1 activity store: actor namespace round-trip, payload-free friction reconstructed from the persisted code, and agreement with the package's friction table |
230
233
  | `downstream-oauth.test.ts` | `KvOAuthProvider` round-trips and races, `auth_required` versus `error`, `startAuth`/`finishAuth`, callback refusal equality, bounded diagnostics, and HTML escaping |
231
234
  | `errors.test.ts` | `ConnectorCallError` codes, retryable defaults and overrides, `retryAfterMs` round-trip, typed-over-heuristic classification, `AbortError` as a retryable timeout, and framing errors |
232
- | `execute.test.ts` | the code-mode host bridge: identifier sanitization, MCP-result unwrapping, sandbox provider construction, fail-closed filtering of destructive and unannotated tools, and MCP/code-mode invocation parity |
235
+ | `execute.test.ts` | the code-mode host bridge: identifier sanitization, MCP-result unwrapping, sandbox provider construction, authenticated thrown-failure framing, fail-closed filtering of destructive and unannotated tools, and MCP/code-mode invocation parity |
233
236
  | `execute-emit.test.ts` | `connecta.emit` (M1–M10) — block validation, budgets, the provider, delivery after the result envelope on success only, and the defaults |
234
237
  | `execute-ui.test.ts` | `connecta.ui` (U1–U9) — validation, multiplicity and budget, the provider, `_meta` delivery, and the Apps shell |
235
238
  | `executor-admission.test.ts` | the portable bounded FIFO both pools use: active and queue ceilings, stable retryable overload, queue timeout, cancellation removal, idempotent release, shutdown |
236
239
  | `guarded-fetch.test.ts` | the guarded transport — construction, request building, destination confinement, and response handling |
237
- | `guest-api-contract.test.ts` | the executor-independent half of the guest API contract cases, including the serialized truncation envelope, capped logs, truncation reported as success, and an in-flight host call failing when the run ends |
240
+ | `guest-api-contract.test.ts` | the shared guest contract on the Dynamic Worker, including caught call, discovery, utility, batch, and budget failure codes; plus the real authority boundary local `data:` fetch, denied egress, unresolved DNS, empty environment paths, unavailable filesystem/HTTP builtins, and present runtime globals |
238
241
  | `linear-provider.test.ts` / `linear-registry.test.ts` | the Linear proxy's construction, classification, and guide; then the same connector inside a real deployment |
239
- | `meta-tools.test.ts` | the registry-backed meta-tools: bounded discovery with page and address maxima, concise and full descriptions, compact and JSON schemas, structured errors, `skills` and connector-guide selection, stored-credential drift, catalog-lookup health accounting, `fields` selection, truncation and `get_result` offset validation and character alignment, per-connector `maxResultBytes`, probe timeouts, and empty-query browse of an unavailable or unconfigured catalog |
240
- | `mixpanel-provider.test.ts` / `mixpanel-registry.test.ts` | the Mixpanel proxy, then the same connector inside a real deployment |
242
+ | `meta-tools.test.ts` | the registry-backed meta-tools: bounded discovery with page and address maxima, concise and full descriptions, compact and JSON schemas with numeric and string constraints, structured errors, `skills` and connector-guide selection including paragraph-aware summaries and configured-summary construction bounds, stored-credential drift, catalog-lookup health accounting, `fields` selection including total and partial misses below nested arrays, truncation and `get_result` offset validation and character alignment, per-connector `maxResultBytes`, probe timeouts, and empty-query browse of an unavailable or unconfigured catalog |
243
+ | `mixpanel-provider.test.ts` / `mixpanel-registry.test.ts` | the Mixpanel proxy, its conditional-input guide and complete reviewed schema-digest manifest, then the same connector inside a real deployment |
241
244
  | `notion-provider.test.ts` / `notion-registry.test.ts` | Notion's tool surface, request construction, lean projections, both pagination conventions, error mapping, and writes; then the connector in a real deployment |
242
245
  | `operator-boundary.test.ts` | the operator row of the decisions table, after every mutation route: authentication material managed without moving a declared structure, and the one honest exception — a credential write making a remote catalog appear, which is discovery arriving, not an operator editing the deployment |
243
246
  | `operator-store.test.ts` | `src/operator-ui/app/store.ts` against a fake browser: the Clerk listener, `gate()`, the generation fence, and the request path |
244
- | `provider-conventions.test.ts` | the conventions a test can hold: hand-written providers refusing schemas they cannot enforce (H5), Cloudflare stating its second pagination convention in the schema (H10), and Notion saying it has no escape hatch (H14) |
245
- | `registry.test.ts` | construction and id validation, startup convention and result-cap warnings, address resolution, tool-cache TTL, and broken-connector isolation |
247
+ | `provider-conventions.test.ts` | the conventions a test can hold: hand-written providers refusing schemas they cannot enforce (H5), their compact discovery schemas staying complete (H7), Cloudflare stating its second pagination convention in the schema (H10), and Notion saying it has no escape hatch (H14) |
248
+ | `registry.test.ts` | construction and id validation, startup warnings, address resolution, catalog TTL/persistence/completeness, agent-only stale-while-revalidate with cross-request single-flight shared with blocking reads in both start orders, owned teardown, invalidation/fingerprint guards, blocking diagnostics, and broken-connector isolation |
246
249
  | `remote-mcp.test.ts` | `remoteMcp()` against an in-process server through the `_transportFactory` seam: passthrough, downstream `isError`, Workers-safe output-schema validation, request-scoped client reuse and at-most-once scope close; plus the real transport's manual redirect policy, destination guard, credential containment, and downstream session termination |
247
250
  | `remote-mcp-pagination.test.ts` | the `tools/list` cursor chain in both directions — exact cursor handoff, first-wins dedup, a failed later page rejecting rather than returning its prefix, the runaway backstops, the tool-metadata re-prime across pages, and paginated catalogs reaching the discovery path |
248
251
  | `request-admission.test.ts` | `/mcp` bounded before auth, the stable 503 and `Retry-After`, health and operator responsiveness under saturation, payload-free counters, queued cancellation, shutdown rejection while active work drains, and the separate fallback code pool |
249
- | `server.test.ts` | end-to-end `/mcp` (401 → initialize instructions → seven tools → usage skill → `call_tool`), the open routes, Clerk `.well-known` metadata with no network, and an end-to-end code-mode run |
252
+ | `server.test.ts` | end-to-end `/mcp` (401 → initialize instructions → seven tools → usage skill → `call_tool`), the open routes, Clerk `.well-known` metadata with no network, an end-to-end code-mode run, and `waitUntil` reaching agent catalog reads through both `search_tools` and `execute_code` |
250
253
  | `server-route-contracts.test.ts` | the route contracts `server.ts` must keep byte-identical: every built-in answered ahead of connector routes inside the security wrapper, open data-free shells with framing denied, per-route auth and same-origin requirements with exact 401/403/405 bodies, and OAuth `verifyState`-before-`finishAuth` ordering |
251
254
  | `startup-warnings.test.ts` | every construction-time `logger.warn` and, as importantly, the conditions that must *not* trigger one: open mode with a credential or OAuth connector, `publicUrl` unset beside OAuth, dropped branding and `uiAuth` URLs, a missing `verifyState`, a credential test-hook mismatch, and an unusable `calls.maxResultBytes` |
252
- | `stripe-provider.test.ts` / `stripe-registry.test.ts` | the Stripe proxy's endpoint modes and admission, then the connector in a real deployment |
253
- | `ui.test.ts` | the server shell and `/ui/*` routes and the app's pure state rules from `view.ts` — filtering, page routing and capability states, credential management, gated `/ui/data` with broken-connector isolation, and the URL safety gates |
255
+ | `stripe-provider.test.ts` / `stripe-registry.test.ts` | the Stripe proxy's endpoint modes, admission, multi-account OAuth guidance, and no-guess account selection; then the connector in a real deployment |
256
+ | `ui.test.ts` | the server shell and `/ui/*` routes and the app's pure state rules from `view.ts` — filtering, page routing and capability states, credential management, gated `/ui/data` with broken-connector isolation and registry-owned catalog-observation containment, and the URL safety gates |
254
257
  | `validate.test.ts` | `validateToolInput()` — a returned (not thrown) `invalid_args` naming the path, `additionalProperties: false` enforcement, per-schema validator caching, and an unusable schema passed through with one warning |
255
258
 
256
259
  ### Node-bound (`NODE_ONLY_SUITES`)
@@ -260,12 +263,12 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
260
263
 
261
264
  | Suite | Covers | Why Node |
262
265
  | --- | --- | --- |
263
- | `deployment-shapes.test.ts` | the Worker as the only example, one Node template that is also its own container, the same source running locally and in the container, the full operator surface in both, a template that cannot start on its own `.env.example`, a Worker README naming every optional peer its entrypoint imports, and the initializer's `.gitignore` staying in step | walks the template and example trees with Node filesystem APIs |
266
+ | `deployment-shapes.test.ts` | the Worker as the only example with a loader-only sandbox, one Node template that is also its own container, the same source running locally and in the container, the full operator surface in both, a template that cannot start on its own `.env.example`, a Worker README naming every optional peer its entrypoint imports, and the initializer's `.gitignore` staying in step | walks the template and example trees with Node filesystem APIs |
264
267
  | `doc-links.test.ts` | the documentation checker itself — local file and fragment resolution, repository URLs resolved back to the checkout, duplicate heading slugs, fenced-code exclusion, and useful failures | spawns the Node checker against filesystem fixtures |
265
268
  | `doctor-cli.test.ts` | `connecta doctor`'s executor line end to end — the sandbox the deployment reports is the one named, an unidentifiable executor gets an executor-neutral line, and a hostile name is bounded and stripped before it reaches a terminal | spawns the CLI against a Node HTTP deployment over real sockets |
266
- | `drift-check.test.ts` | the maintainer drift checker — recorded touched endpoints, a quiet revision bump, clear failures for an unavailable spec/manifest/credential, `$ref` traversal, and one well-formed row per endpoint | spawns the Node checker against filesystem fixtures |
269
+ | `drift-check.test.ts` | the maintainer drift checker — hosted-provider credential framing, recorded touched endpoints, a quiet revision bump, clear failures for an unavailable spec/manifest/credential, `$ref` traversal, and one well-formed row per endpoint | spawns the Node checker against filesystem fixtures |
267
270
  | `file-storage.test.ts` | `fileStorage()` across instances, logical TTL plus physical pruning without clobbering a newer value, and corrupt-file quarantine | exercises the Node filesystem storage adapter |
268
- | `guest-api-contract-quickjs.test.ts` | the shared guest-contract cases on the real QuickJS executor | runs the contract cases on the Node QuickJS executor |
271
+ | `guest-api-contract-quickjs.test.ts` | the shared guest-contract cases on the real QuickJS executor, including identical caught failure codes, its exact absent globals, and blocked runtime imports | runs the contract cases on the Node QuickJS executor |
269
272
  | `node.test.ts` | the `listen()` adapter propagating an HTTP client disconnect through the Web `Request` and the MCP handler into a program's connector call, releasing both admission permits | exercises the Node HTTP adapter over real TCP sockets |
270
273
  | `packed-links.test.ts` | the packed-link gate itself — shipped targets and repository URLs accepted, relative links into unshipped paths and directories rejected with the citation to write instead, reference definitions seen, fenced examples ignored, the changelog exempt | spawns the Node packed-link gate against filesystem fixtures |
271
274
  | `package-surface.test.ts` | the published boundary — built output shipped, the `exports` map carrying exactly the documented subpaths plus `./package.json`, only generic factories, platform storage kept in examples, Clerk and QuickJS behind optional subpaths, every provider independently importable, and the Cloudflare provider free of bare specifiers | walks the package tree with Node filesystem APIs |
@@ -283,7 +286,7 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
283
286
 
284
287
  | Suite | Covers |
285
288
  | --- | --- |
286
- | `browser/operator-ui.spec.ts` | the operator wiring in a real browser: the shell staying open until authentication, credential and access-token and OAuth flows end to end, drift shown without naming a tool, and every failure and empty state |
289
+ | `browser/operator-ui.spec.ts` | the operator wiring in a real browser: Clerk loader order across its version redirect and a real load failure, the shell staying open until authentication, credential and access-token and OAuth flows end to end, drift shown without naming a tool, and every failure and empty state |
287
290
  | `browser/program-ui.spec.ts` | the Apps shell in a real browser: a bound view merging fixed and declared arguments and correlating concurrent reads, and the one-string payload receiving no read bridge ([program UI read calls](./program-ui-read-calls.md)) |
288
291
 
289
292
  **The `_transportFactory` seam.** `RemoteMcpOptions._transportFactory` is
@@ -44,6 +44,14 @@ every operator-configured URL stay in `src/ui.ts`, where they are gated before
44
44
  they can become an attribute; the bundle renders everything that has a state.
45
45
  Two roots share one store: `#operatorNav` and `#operatorContent`.
46
46
 
47
+ The Clerk loader is intentionally blocking. The inline operator bundle calls
48
+ `boot()` as soon as the parser reaches the end of the body, so a deferred Clerk
49
+ script would make an expected parse-time gap look like a permanent network
50
+ failure. Blocking also preserves the existing failure path: after a real
51
+ loader error, the parser continues and `boot()` renders the Clerk load message.
52
+ Clerk's redirect from the major-version loader URL to its pinned asset keeps
53
+ the same ordering.
54
+
47
55
  ## Rules that are not obvious
48
56
 
49
57
  - **No operator data in the shell.** Every page serves the same markup. Connector,
@@ -93,9 +101,11 @@ well as Node and there is no DOM in either:
93
101
  something calls them when the identity actually changes. It typechecks in the
94
102
  DOM-lib program (`tsconfig.operator-ui.json`) because it imports the store.
95
103
  - `test/browser/operator-ui.spec.ts` — the wiring, in a real browser:
104
+ Clerk loader order across its version redirect and a real load failure, plus
96
105
  credential, token, and OAuth flows end to end, including their failure and
97
- empty states. Run it with `npm run test:browser` (`npm run test:browser:install`
98
- once, for Chromium). It is not part of `npm run check`.
106
+ empty states. Run it with `npm run test:browser`
107
+ (`npm run test:browser:install` once, for Chromium). It is not part of
108
+ `npm run check`.
99
109
 
100
110
  ## Why the bundle is committed
101
111
 
@@ -104,14 +104,14 @@ deliberate surface.
104
104
  | Convention | Verdict | Notes |
105
105
  | --- | --- | --- |
106
106
  | P1 add, never rewrite | meets | annotations only |
107
- | P2 identity | meets | required `purpose`, `instructions` appended, classification untouchable from there |
107
+ | P2 identity | meets | required `purpose`, `instructions` appended, classification untouchable from there; purpose states deployment routing intent and the guide says it is not proof of authenticated account identity |
108
108
  | P3 routing fact | meets | production versus sandbox appears in the title, the description, and the guide's first line |
109
109
  | P4 endpoint default | meets | exemplary, and the model for the second clause: one published endpoint, `mode` required with no default, and construction throws when a recognizable key prefix contradicts the declared mode without reading anything it cannot classify |
110
110
  | P5 classification | meets | including the two verdicts that needed an argument — `stripe_api_read` is a read because the tool is the boundary, `create_refund` is destructive despite its name |
111
111
  | P6 catalog varies | **missed → fixed** | the doc already knew this (`get_balance_summary` is Treasury and gated; a `create_customer` example survives in Stripe's prose but not its tool table), but the *guide* did not say it, and the guide is what reaches the agent. Added |
112
112
  | P7 reduction advice | **missed → fixed** | bare string; the derived summary was "Mode: production. Account purpose: …", spending the 120-character budget on the operator's prose. Now a declared, mode-shaped summary. `required` unset: the four generic tools are the routing decision and the mode warning already rides the title and description |
113
- | P8 identity resolution | **missed → fixed** | Stripe's writes take ids and the guide never said where they come from. Added: the typed prefixes (`cus_`, `sub_`, `ch_`, `pi_`, `in_`, `acct_`), the rule that a plausible-looking id belongs to a different object or to nobody, and the read tools that produce a real one |
114
- | P9 authentication | meets | OAuth default, `requireHttps`, restricted key documented as a secret and paired with the narrowest scope. The `auth_required` → `authorize_connector` route was added to the guide alongside P8, since a proxy's only recovery instruction lives there |
113
+ | P8 identity resolution | **missed → fixed** | Stripe's writes take ids and the guide never said where they come from. Added: the typed prefixes (`cus_`, `sub_`, `ch_`, `pi_`, `in_`, `acct_`), the rule that a plausible-looking one belongs to a different object or to nobody, and the read tools that produce a real one. The guide now also accounts for OAuth sessions tied to several organization accounts: connector metadata is not identity proof, the exact selector must come from the live tool schema, and an ambiguous target or selection mechanism stops rather than becoming a guessed argument or header ([#404](https://github.com/zackbart/connecta/issues/404)) |
114
+ | P9 authentication | meets | OAuth default, `requireHttps`, restricted key documented as a secret and paired with the narrowest scope. The guide distinguishes organization accounts within an OAuth session from Connect connected accounts, whose calls reject OAuth and use a deployment-configured restricted key plus `Stripe-Account`. The `auth_required` → `authorize_connector` route was added alongside P8, since a proxy's only recovery instruction lives there |
115
115
  | P10 no credential test | meets | no credential slot; the mode/key contradiction throws at construction instead, which is where P10 says the H12 guarantee gets paid |
116
116
  | P11 transport vs tool error | meets | inherited from `remoteMcp()`; the guide now also says that a rejected argument or plan restriction arrives in Stripe's own words and is not an authorization problem |
117
117
  | P12 admission budget | meets | a citable documented number (100/s live, 25/s sandbox), transcribed per mode, with `maxConcurrency` labeled as Connecta's own conservative choice |
@@ -50,19 +50,24 @@ floor. From `src/catalog.ts` and `src/catalog-service.ts`:
50
50
  `fullDescriptions: true`. Prose past those points reaches an agent only when
51
51
  it pays for the expansion.
52
52
  - **A compact schema renders into at most 1,024 UTF-8 bytes**, and any single
53
- enum node into at most 256. Past either cap the renderer keeps what fits and
54
- degrades the rest a prefix of the enum plus `unknown`, a required-first
55
- object with `unknown` types, or `unknown /* truncated */`and flags the
56
- match, which costs a describe round trip to recover.
53
+ enum or constraint annotation into at most 256. Numeric bounds, string
54
+ length bounds, patterns, and formats ride beside their TypeScript-like type.
55
+ Past a cap the renderer keeps what fits and degrades the rest a prefix of
56
+ the enum plus `unknown`, a shape without the constraints that did not fit, a
57
+ required-first object with `unknown` types, or
58
+ `unknown /* truncated */` — and flags the match, which costs a describe
59
+ round trip to recover.
57
60
  - **`inputKeys`, `requiredInputKeys`, and `outputKeys` come only from bounded
58
61
  plain-object schemas.** A top-level `anyOf` has no keys to list, so a caller
59
62
  learns nothing about the arguments without expanding the schema.
60
- - **A guide summary is capped at 120 characters**, defaulting to the guide's
61
- first meaningful body line.
63
+ - **A guide summary is bounded at 120 characters.** A configured value past
64
+ the bound refuses construction. An omitted one defaults to the guide's first
65
+ meaningful body paragraph, joined across physical line wraps and shortened
66
+ at a readable boundary.
62
67
  - **Search returns a connector's `id`, `title`, `guide`, and `guideSummary` —
63
68
  never its `description`.** The description reaches an agent only as the
64
- fallback summary for a guide with no usable body line. Routing facts belong
65
- in the title and the guide's first line; a routing fact that lives only in
69
+ fallback summary for a guide with no usable body paragraph. Routing facts belong
70
+ in the title and the guide's opening paragraph; a routing fact that lives only in
66
71
  the connector description has been written into a field the model does not
67
72
  read. Neither the `id` nor the `title` is a lexical document, so a term drawn
68
73
  from one of them is not a search hit — it is a no-match whose guidance names
@@ -141,7 +146,8 @@ wrong-tool selection.
141
146
  Every tool carries a hand-written `inputSchema`: a plain object at the top
142
147
  level, `additionalProperties: false`, an accurate `required` list, an `enum` on
143
148
  every constrained field, explicit numeric bounds on every page size and count,
144
- and a description on every property nested objects and array items included,
149
+ explicit string bounds where length or shape is constrained, and a description
150
+ on every property — nested objects and array items included,
145
151
  because a caller composing an array element is reading that element's fields,
146
152
  not the parent's prose. `api()` enforces the enforceability half for free since
147
153
  [#340](https://github.com/zackbart/connecta/issues/340): a schema the validator
@@ -165,10 +171,12 @@ argument retries.
165
171
  ### H7 — Schemas fit the compact renderer, or selection does not depend on the part that is cut
166
172
 
167
173
  Keep the common path's compact input and output shapes inside 1,024 bytes and
168
- each enum node inside 256. Where a legitimate enum genuinely cannot fit — 21
169
- DNS record types the truncation is acceptable only if the tool's name and
170
- description already carry enough for selection, so the caller expands the
171
- schema to *call*, not to *choose*.
174
+ each enum or constraint annotation inside 256. Numeric and string constraints
175
+ render when they fit. Search drops complete constraints that do not fit and
176
+ sets the existing truncation flag; compact describe keeps them. Where a
177
+ legitimate enum genuinely cannot fit — 21 DNS record types — the truncation is
178
+ acceptable only if the tool's name and description already carry enough for
179
+ selection, so the caller expands the schema to *call*, not to *choose*.
172
180
 
173
181
  *Why:* a truncated compact shape costs a describe round trip. *Cost:* discovery
174
182
  tokens.
@@ -561,6 +569,11 @@ One credential per provider comes from the environment —
561
569
  `CONNECTA_DRIFT_LINEAR_KEY`, `CONNECTA_DRIFT_STRIPE_KEY`,
562
570
  `CONNECTA_DRIFT_MIXPANEL_KEY` — and a missing or dead one stops the run with a
563
571
  message naming it rather than reporting an empty catalog as mass removal.
572
+ Linear and bare Stripe values use their documented bearer or Basic framing.
573
+ Mixpanel's beta service-account form is provider-specific:
574
+ `user:secret` becomes `Bearer Basic <base64(user:secret)>`, exactly as its MCP
575
+ documentation requires. A value that already includes whitespace is treated
576
+ as a complete Authorization value and passes through unchanged.
564
577
 
565
578
  **Touched endpoints.** A hand-written provider is written against a published
566
579
  OpenAPI document and calls a few dozen of its operations, so
@@ -17,9 +17,15 @@ const billing = stripe("stripe_live", {
17
17
  });
18
18
  ```
19
19
 
20
- The `id` owns the ordinary connector namespaces; use a different id for every
21
- Stripe account. `purpose` is required because an agent choosing between two
22
- instances needs to know which account answers the question. Account
20
+ The `id` owns the ordinary connector namespaces. Choose a connector boundary
21
+ for its credential or OAuth session, mode, and business purpose — not
22
+ automatically for each Stripe account. One OAuth session may cover more than
23
+ one account in the same Stripe organization. Use separate connectors when the
24
+ credential, production/sandbox mode, or business purpose differs.
25
+
26
+ `purpose` is required because it tells an agent where the deployment intends
27
+ to route a question. The connector id, title, and purpose are configuration,
28
+ not proof of which account the authenticated Stripe session will use. Account
23
29
  `instructions` are appended to the maintained guide and cannot change the
24
30
  connector's safety classification.
25
31
 
@@ -53,9 +59,9 @@ one. That check reads nothing it cannot classify — an OAuth connector, or a
53
59
  credential shape this release does not recognize, is left alone rather than
54
60
  guessed at — and the error names only the two modes, never the key.
55
61
 
56
- Deploy both side by side. Two instances are isolated exactly like two
57
- hand-written connectors with different ids: separate addresses, catalogs,
58
- credentials, storage, admission counters, and health.
62
+ Deploy production and sandbox side by side. Two instances are isolated exactly
63
+ like two hand-written connectors with different ids: separate addresses,
64
+ catalogs, credentials, storage, admission counters, and health.
59
65
 
60
66
  ```ts
61
67
  connectors: [
@@ -74,7 +80,20 @@ connectors: [
74
80
 
75
81
  OAuth is the default and the option Stripe recommends: it supports dynamic
76
82
  client registration and PKCE, and each connector instance keeps its own flow
77
- and tokens in connector-scoped storage. Stripe also accepts a
83
+ and tokens in connector-scoped storage. Stripe's current
84
+ [session-management documentation](https://docs.stripe.com/mcp#manage-mcp-client-sessions)
85
+ says one OAuth session can be tied to more than one account in the same Stripe
86
+ organization. It does not say every session has multiple accounts.
87
+
88
+ That scope changes what an agent must prove before an account-scoped call. It
89
+ must resolve the intended organization account, inspect the selected tool's
90
+ live input schema, and carry only the exact account or context field that
91
+ schema exposes. If more than one account fits, or the live schema exposes no
92
+ clear selection mechanism, the agent stops and asks. It never guesses from the
93
+ connector id, title, or purpose, and it never invents an MCP argument or
94
+ request header.
95
+
96
+ Stripe also accepts a
78
97
  [restricted API key](https://docs.stripe.com/keys#create-restricted-api-key) as
79
98
  a bearer token for headless agents:
80
99
 
@@ -93,9 +112,12 @@ Use a restricted key, not a secret key, and scope it to the operations the
93
112
  agent actually needs; Stripe's own guidance is to "limit your agent's access to
94
113
  exactly the functionality it requires". Keep it in the runtime's secret store.
95
114
 
96
- Connect platforms can act as a connected account with `connectedAccount`, which
97
- adds Stripe's `Stripe-Account` header. Stripe does not support OAuth for
98
- connected-account calls, so this requires `headers` auth and throws otherwise:
115
+ Organization accounts in one OAuth session are not Stripe Connect connected
116
+ accounts. Connect platforms can act as a connected account with
117
+ `connectedAccount`, which adds Stripe's documented `Stripe-Account` header at
118
+ connector construction. Stripe does not support OAuth for connected-account
119
+ calls, so this requires a restricted key through `headers` auth and throws
120
+ otherwise:
99
121
 
100
122
  ```ts
101
123
  stripe("merchant_42", {
@@ -109,9 +131,10 @@ stripe("merchant_42", {
109
131
  });
110
132
  ```
111
133
 
112
- Administrators must enable MCP access in the Stripe Dashboard, and Stripe
113
- manages that setting **separately for sandbox and live mode**. A connector that
114
- boots but cannot list tools is usually a dashboard toggle, not a bad key.
134
+ Administrators must enable MCP access in the Stripe Dashboard. Stripe scopes
135
+ OAuth session management and MCP access **separately for sandbox and live
136
+ mode**. A connector that boots but cannot list tools is usually a dashboard
137
+ toggle, not a bad key.
115
138
 
116
139
  ## The eleven tools, and what they are classified as
117
140
 
@@ -158,7 +181,7 @@ serves, an unclassified and unannotated `create_customer` lands on the approval
158
181
  path. Expect the undocumented Treasury tools Stripe alludes to to arrive
159
182
  unclassified as well — annotated ones will be taken at their word.
160
183
 
161
- The upshot is that this account's tool list is not a fixed set, and the usage
184
+ The upshot is that this connection's tool list is not a fixed set, and the usage
162
185
  guide tells the agent so: search this connector for what it actually exposes
163
186
  rather than assuming a documented tool is present. The guide also names the id
164
187
  discipline the downstream schemas cannot enforce — Stripe ids are typed
@@ -166,6 +189,14 @@ prefixes (`cus_`, `sub_`, `ch_`, `pi_`, `in_`, `acct_`), a plausible-looking one
166
189
  belongs to a different object or to nobody, and the id a write takes comes from
167
190
  `stripe_api_search` or a list read rather than from a guess.
168
191
 
192
+ Account selection comes before that object-id rule. The served guide warns
193
+ that the connector metadata states routing intent rather than authenticated
194
+ identity. It tells the agent to use only selectors in the live tool schema and
195
+ to stop when the account or selection mechanism is ambiguous. It also keeps
196
+ organization-account selection separate from the restricted-key-only Connect
197
+ path, so an agent cannot repair uncertainty by fabricating `Stripe-Account` as
198
+ a tool argument.
199
+
169
200
  Stripe publishes no stability or deprecation policy for this tool set and
170
201
  invites tool requests by email, so treat the list as unversioned. `get_balance_summary`
171
202
  is Treasury, which Stripe labels public preview and gates behind an access
@@ -57,7 +57,7 @@ exist so far:
57
57
  | --- | --- | --- |
58
58
  | **pre-template** | before 0.10.2 | no `connecta init` existed; hand-written, or copied from the retired `examples/node` |
59
59
  | **A** | 0.10.2 – 0.15.1 | `.env.example`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `README.md`, `package.json`, `src/index.ts`, `tsconfig.json` |
60
- | **B** | 0.16.0 – 0.16.1 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
60
+ | **B** | 0.16.0 – 0.17.0 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
61
61
 
62
62
  Generation A is a decade in template years and identifying it precisely does
63
63
  not matter, because you are about to reconstruct it exactly rather than guess
@@ -106,7 +106,7 @@ know what to preserve, once to know what to re-verify at the end.
106
106
  ### Bump the pin and install
107
107
 
108
108
  ```sh
109
- npm pkg set dependencies.@zackbart/connecta=0.16.1
109
+ npm pkg set dependencies.@zackbart/connecta=0.17.0
110
110
  npm install
111
111
  ```
112
112
 
@@ -130,7 +130,7 @@ Generate the *current* template beside the base you already made, into the same
130
130
  `$SCRATCH`:
131
131
 
132
132
  ```sh
133
- (cd "$SCRATCH" && npx @zackbart/connecta@0.16.1 init current)
133
+ (cd "$SCRATCH" && npx @zackbart/connecta@0.17.0 init current)
134
134
  ```
135
135
 
136
136
  You now have a three-way merge with a real base: `$SCRATCH/base` is what this
@@ -186,7 +186,7 @@ A deployment older than 0.10.2 has no base to diff against. Do not try to
186
186
  manufacture one. Instead:
187
187
 
188
188
  1. `SCRATCH=$(mktemp -d)`, then
189
- `(cd "$SCRATCH" && npx @zackbart/connecta@0.16.1 init current)` — there is no
189
+ `(cd "$SCRATCH" && npx @zackbart/connecta@0.17.0 init current)` — there is no
190
190
  `base` leg here, only the current template to read from.
191
191
  2. Copy `$SCRATCH/current` into the deployment file by file, **skipping
192
192
  `src/index.ts`**.
@@ -207,6 +207,30 @@ first, so cross them bottom-up: start at the oldest one still above this
207
207
  deployment's pin and work back up the page, because each boundary assumes the
208
208
  older ones are already done.
209
209
 
210
+ ### 0.16.1 → 0.17.0
211
+
212
+ Two construction rules need a deployment check.
213
+
214
+ **A Dynamic Worker executor is loader-only.** The supported construction is
215
+ exactly:
216
+
217
+ ```ts
218
+ new DynamicWorkerExecutor({ loader: env.LOADER })
219
+ ```
220
+
221
+ Remove `bindings`, `modules`, or `globalOutbound` from that options object.
222
+ Those fields grant guest code ambient configuration, code, or egress. This is
223
+ the supported sandbox boundary rather than a new Connecta-side inspection of
224
+ the third-party executor object. The shipped Worker example was already
225
+ loader-only, and Node deployments use `quickJsExecutor()`, so neither needs a
226
+ change (#390).
227
+
228
+ **An explicit guide summary must fit discovery.** Connecta normalizes
229
+ whitespace in `usageGuide.summary` and refuses registry construction when the
230
+ result is longer than 120 characters. Shorten it to 120 characters or fewer,
231
+ or omit it and let Connecta derive a bounded summary from the guide's opening
232
+ prose. A blank explicit summary still takes the derived-summary path (#392).
233
+
210
234
  ### 0.16.0 → 0.16.1
211
235
 
212
236
  Nothing throws, and a Node deployment crosses this on the version bump alone.