@zackbart/connecta 0.16.1 → 0.18.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 (46) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +4 -0
  3. package/dist/catalog-service.d.ts +10 -0
  4. package/dist/catalog-service.js +77 -5
  5. package/dist/catalog.js +114 -12
  6. package/dist/errors.d.ts +4 -6
  7. package/dist/execute.d.ts +7 -0
  8. package/dist/execute.js +262 -168
  9. package/dist/invocation.js +3 -1
  10. package/dist/meta-tools.d.ts +4 -0
  11. package/dist/meta-tools.js +55 -23
  12. package/dist/operator-ui/generated.d.ts +1 -1
  13. package/dist/operator-ui/generated.js +1 -1
  14. package/dist/operator-ui/model.d.ts +3 -1
  15. package/dist/providers/mixpanel.d.ts +3 -5
  16. package/dist/providers/mixpanel.js +73 -5
  17. package/dist/providers/stripe.d.ts +25 -24
  18. package/dist/providers/stripe.js +64 -35
  19. package/dist/registry.d.ts +32 -9
  20. package/dist/registry.js +217 -33
  21. package/dist/routes/mcp.js +6 -0
  22. package/dist/routes/ui.js +1 -1
  23. package/dist/skills.d.ts +5 -1
  24. package/dist/skills.js +206 -30
  25. package/dist/types.d.ts +14 -2
  26. package/dist/ui.js +4 -1
  27. package/dist/version.d.ts +1 -1
  28. package/dist/version.js +1 -1
  29. package/documentation/architecture.md +8 -5
  30. package/documentation/code-mode.md +68 -68
  31. package/documentation/connector-guides.md +29 -27
  32. package/documentation/connectors.md +13 -1
  33. package/documentation/meta-tools.md +53 -19
  34. package/documentation/mixpanel.md +20 -0
  35. package/documentation/notion.md +17 -0
  36. package/documentation/operations.md +24 -21
  37. package/documentation/operator-ui.md +12 -2
  38. package/documentation/provider-audit.md +15 -7
  39. package/documentation/provider-conventions.md +26 -13
  40. package/documentation/stripe.md +66 -59
  41. package/documentation/upgrading.md +46 -4
  42. package/ethos.md +7 -7
  43. package/examples/worker/README.md +4 -3
  44. package/package.json +2 -2
  45. package/templates/node/README.md +7 -0
  46. package/templates/node/package.json +5 -2
@@ -53,8 +53,8 @@ createConnecta({
53
53
  });
54
54
  ```
55
55
 
56
- Dynamic Workers require the Workers Paid plan. The complete required binding and
57
- package setup is in the [Worker example](../examples/worker/README.md#code-mode).
56
+ Dynamic Workers require the Workers Paid plan. The supported constructor passes only `loader`; `bindings`, `modules`, or
57
+ `globalOutbound` grant ambient guest authority and violate `P2`. The [Worker example](../examples/worker/README.md#code-mode) carries the full setup.
58
58
 
59
59
  ## What an executor must implement
60
60
 
@@ -104,8 +104,8 @@ Connecta passes exactly one provider, named `connecta`. An executor must:
104
104
  uncaught tool failure keeps its type (`E1`).
105
105
  6. **Capture `console.log`, `console.warn`, and `console.error`** into `logs` in
106
106
  call order (`R5`), bounding what it retains.
107
- 7. **Bound the guest**: wall clock, memory, stack, and CPU (`L3`, `L5`), with no
108
- network, filesystem, environment, or import capability (`P2`).
107
+ 7. **Bound the guest**: wall clock, memory, stack, and CPU (`L3`, `L5`). Keep
108
+ ambient capabilities within the documented and tested `P2`/`X5` boundary.
109
109
  8. **Grant no ambient authority of its own.** Never back this with `eval` or
110
110
  `node:vm`: the sandbox is a containment layer on top of connecta's boundary,
111
111
  not a replacement for it, and every capability arrives through `fns`.
@@ -138,10 +138,10 @@ reinterpreted, so do not rely on it.
138
138
  It is host plumbing, callable but not contract: it takes a connector id and an
139
139
  unsanitized-or-sanitized tool name and can change shape without notice.
140
140
 
141
- Anything else a runtime happens to expose is outside the contract and must not
142
- be used, even where it exists. Neither executor grants network egress,
143
- filesystem access, credentials, or deployment configuration; what they leave
144
- lying around otherwise differs (`X5`).
141
+ Anything else a runtime happens to expose is outside the portable contract and
142
+ must not be used. QuickJS grants none of it. A loader-only Dynamic Worker denies
143
+ external egress and filesystem access and keeps its environment maps empty, but
144
+ it exposes the globals and runtime builtins described in `X5`.
145
145
 
146
146
  **P3.** Values cross the host bridge as JSON. Arguments must be
147
147
  JSON-serializable and results arrive as plain JSON values. A value outside JSON —
@@ -154,8 +154,9 @@ scratch storage carried to the next program, and no request-bound object outlive
154
154
  the request that created it. Within one execution, host calls share one
155
155
  downstream request scope.
156
156
 
157
- **P5.** Plain JavaScript only. TypeScript syntax is a syntax error, and there is
158
- no `import` or `require` to reach for.
157
+ **P5.** Plain JavaScript only. TypeScript syntax is a syntax error. Portable code
158
+ does not import: QuickJS blocks imports, while Dynamic Workers expose the `X5`
159
+ runtime modules. Neither executor exposes `require`.
159
160
 
160
161
  ## Addressing
161
162
 
@@ -172,7 +173,10 @@ global whose properties are its tools, so `<connectorId>.<toolName>(args)` works
172
173
  with both parts sanitized into JavaScript identifiers — characters outside
173
174
  `[A-Za-z0-9_$]` become `_`, a leading digit gets `_` prefixed, and a reserved
174
175
  word gets `_` appended (`my-service.get.thing` → `my_service.get_thing`). The
175
- globals are lazy: no catalog is fetched until a program touches one.
176
+ globals are lazy: no catalog is fetched until a program touches one. The
177
+ bounded deployment inventory in the `execute_code` description shows each
178
+ canonical connector id and labels the shortcut only when it differs; the
179
+ [discovery guide](./meta-tools.md#discovery-context) defines that bound.
176
180
 
177
181
  **A3.** A shortcut that resolves to more than one tool fails closed with
178
182
  `ambiguous_tool_alias`, naming the colliding tool names and pointing at
@@ -240,8 +244,8 @@ can set `includeSchemaKeys: false` to buy the bytes back.
240
244
  **S3.** Discovery is bounded and the bounds throw rather than silently shrink: a
241
245
  `limit` outside 1–100 is `invalid_args`, and a page whose serialized form
242
246
  exceeds 256,000 bytes is `result_too_large`, each with a hint naming the ways to
243
- ask for less. As with every failure, the *thrown* error carries only the message
244
- (`E1`); the code appears when the failure escapes the program uncaught.
247
+ ask for less. The thrown error carries the stable `code`, `retryable`, and
248
+ `details` fields (`E1`).
245
249
 
246
250
  ### connecta.describe
247
251
 
@@ -255,10 +259,13 @@ const { tools } = await connecta.describe({
255
259
  });
256
260
  ```
257
261
 
258
- **S4.** Returns `{ tools }` in the order asked, one entry per address. An
259
- address that is unknown, or whose connector's catalog could not be loaded,
260
- returns an entry carrying `error` one bad address never fails the whole call.
261
- More than 100 addresses is `invalid_args`; the same 256,000-byte ceiling applies.
262
+ **S4.** Returns `{ tools }` in order, one entry per address. An unknown address
263
+ or failed catalog returns `error` plus typed `errorDetails`: `code`, `message`, and `retryable`. Misses
264
+ carry a route-aware `nextAction`; a close miss may add three canonical `suggestions`.
265
+ Catalog failures add only `retryAfterMs` when known. One bad address never fails the whole call. Each failed entry clamps its
266
+ caller-authored `address` to 512 UTF-8 bytes with an `…` marker. Entry order
267
+ correlates a clipped address with its request; successes keep canonical addresses. More than 100
268
+ addresses is `invalid_args`; the same 256,000-byte ceiling applies.
262
269
 
263
270
  ### connecta.call
264
271
 
@@ -295,9 +302,7 @@ order. A success is `{ address, ok: true, data }`. A failure is
295
302
  field names the host's internal batch path uses. One failing call never rejects
296
303
  the batch, and more than ten calls throws.
297
304
 
298
- **S8.** `connecta.batch` is the classification channel: because a thrown host
299
- error crosses the bridge as a bare message (`E1`), a batch of one is the supported
300
- way for a program to *decide* something about a failure rather than report it.
305
+ **S8.** Batch and thrown failures share one vocabulary (`E1`): an entry's `errorDetails.code` and `retryable` equal the fields on the error the same call would throw. Use batch for independent concurrency, not to recover lost type.
301
306
 
302
307
  ### connecta.emit
303
308
 
@@ -310,24 +315,18 @@ clauses are [Emitted output](#emitted-output) (`M1`–`M10`).
310
315
 
311
316
  ## Errors
312
317
 
313
- **E1.** There are four error channels, and only two of them are typed.
318
+ **E1.** There are four error channels. Connecta failures are typed whether caught or uncaught.
314
319
 
315
320
  | Channel | Shape | Typed? |
316
321
  | --- | --- | --- |
317
- | A throw inside the program | `Error` with `message` only | no |
322
+ | A caught Connecta host failure | `Error` with `message`, `code`, `retryable`, and `details` | yes |
318
323
  | `connecta.batch` outcome | `{ ok: false, error, errorDetails }` | yes |
319
324
  | An uncaught **tool or discovery** failure, as the model sees it | `{ error: { code, message, retryable, … } }` with `isError` | yes |
320
- | Anything else that ends the run (`E5`, `E6`, a bridge bound in `L6`) | error text | no |
321
-
322
- The message-only throw is a hard limit of the guest bridge: both executors reduce a rejected
323
- host call to `new Error(message)`, dropping every own property. A program must
324
- therefore never branch on an error's fields and never parse its message. To
325
- classify, use `errorDetails`; to hand a failure to the model with its type
326
- intact, let it escape uncaught — connecta re-attaches the typed details on the
327
- way out. The model-facing version of this lives in `execute_code`'s description,
328
- not in the always-loaded usage skill, which `test/meta-tools.test.ts` caps at
329
- 2,500 bytes — a budget the guide already spends nearly all of, so new text there
330
- displaces old rather than adding to what every request pays for.
325
+ | Program or execution failure (`E5`, `E6`, a bridge bound in `L6`) | error text | no |
326
+
327
+ Both executor bridges reduce a rejected host call to `new Error(message)`. Connecta restores the typed failure in a trusted prelude with a per-execution authenticated frame (`X11`), without turning the rejection into a returned value.
328
+ `message` remains the human text. `code` and `retryable` are the stable branch fields; `details` is the complete host classification. This covers `call`, connector shortcuts, `search`, `describe`, `emit`, `ui`, rejected batch input, and the host-call budget.
329
+ Program-authored errors stay untyped, and code must never parse error prose.
331
330
 
332
331
  **E2.** The taxonomy: `retryable` is what connecta reports, `Y3` what a program may do.
333
332
 
@@ -339,16 +338,18 @@ displaces old rather than adding to what every request pays for.
339
338
  | `destructive_tool_requires_approval` | the tool is not explicitly read-only | false |
340
339
  | `auth_required` | the credential is missing, expired, or rejected | false |
341
340
  | `invalid_args` | arguments or discovery bounds were rejected | false |
342
- | `not_found` | the downstream answered and the resource is not there — the one code that says skip this id rather than stop, classified off `errorDetails` per `E1` and never off a caught error, raised only where the provider tells absence from a permission gap ([H11](./provider-conventions.md#h11--errors-are-mapped-to-what-the-caller-does-next)) | false |
341
+ | `not_found` | the downstream answered and the resource is not there — the one code that says skip this id rather than stop, raised only where the provider tells absence from a permission gap ([H11](./provider-conventions.md#h11--errors-are-mapped-to-what-the-caller-does-next)) | false |
343
342
  | `input_required_unsupported` | a downstream asked for mid-call input | false |
344
343
  | `rate_limited` | the downstream reported a rate limit | true |
345
344
  | `unavailable` | the downstream is down or unreachable | true |
346
345
  | `timeout` | the per-call 15-second deadline expired | true |
347
346
  | `cancelled` | the run ended while this call was in flight (`E5`) | false |
348
- | `connector_call_failed` | anything else the connector threw, and the host-call budget (`L4`) | per message |
347
+ | `connector_call_failed` | anything else the connector threw | per message |
349
348
  | `batch_call_failed` | a `connecta.batch` entry connecta could not even attempt | per message |
350
349
  | `catalog_lookup_failed` | the connector's catalog could not be loaded | per cause |
351
350
  | `result_processing_failed` | the result could not be prepared | per message |
351
+ | `result_too_large` | a discovery response exceeded its byte bound | false |
352
+ | `budget_exceeded` | the run exhausted a host-call or emitted-output budget | false |
352
353
 
353
354
  **E3.** `auth_required` carries the same recovery envelope as `call_tool`:
354
355
  `connector`, `operation`, `recovery` (`oauth`, `operator_config`, or
@@ -382,7 +383,7 @@ exactly first, by containment second — so a program that *wraps* a failure's
382
383
  message in its own text still reports the underlying typed failure. Keeping the
383
384
  type beats keeping the prose.
384
385
 
385
- **E7.** `retryable` for `unknown_address`, `unknown_tool`, `ambiguous_tool_alias`, and `destructive_tool_requires_approval` is pinned false, never inferred from an address containing `503`, `429`, or `temporar`. The first two carry `nextAction: { function: "connecta.search", arguments: { query, connector?, includeSchemas: "compact" } }` — the same scoped discovery the top-level record names, keyed to the surface the caller actually has. A program cannot call `search_tools`, so it is never told to. Both the message and the derived `query` clamp the address to 512 UTF-8 bytes with a `…` marker: the address is caller-authored and lands in the message, the query, the text content, and `structuredContent`, so an invented 50 KB one would otherwise produce a refusal orders of magnitude past the deployment's result cap. A clipped address still identifies the mistake; a short one — the common case — is exact and untagged.
386
+ **E7.** `retryable` for `unknown_address`, `unknown_tool`, `ambiguous_tool_alias`, and `destructive_tool_requires_approval` is pinned false, never inferred from an address containing `503`, `429`, or `temporar`. The first two carry `nextAction: { function: "connecta.search", arguments: { query, connector?, includeSchemas: "compact" } }` — the same scoped discovery the top-level record names, keyed to the surface the caller actually has. A program cannot call `search_tools`, so it is never told to. The message, the derived `query`, and a failed describe entry's `address` clamp caller-authored text to 512 UTF-8 bytes with an `…` marker. Those values land in the text content and `structuredContent`, so an invented 50 KB address would otherwise produce a refusal orders of magnitude past the deployment's result cap. A clipped address still identifies the mistake by its position; a short one — the common case — is exact and untagged.
386
387
 
387
388
  **E8.** A remote MCP tool whose advertised schema rejects the call fails before provider dispatch with `invalid_args`, carrying bounded, value-free `{ path, code, expected }` findings and scoped search recovery keyed `function: "connecta.search"` like every other in-program miss. A declared property reports the schema keyword that failed, never the validator's duplicate `additionalProperties` branch; a truly undeclared property still reports `additionalProperties`. Unsupported schemas pass through; unrecognized provider prose remains `connector_call_failed`.
388
389
 
@@ -590,14 +591,14 @@ the same mistake as automatic host-side projection, refused in `ethos.md`
590
591
  ([#282](https://github.com/zackbart/connecta/issues/282)).
591
592
 
592
593
  **U13.** The always-loaded MCP instructions locate `connecta.ui(html)` before an
593
- agent chooses a route: it is a guest function inside `execute_code`, never a
594
- connector address or catalog result, takes one HTML string, and carries `U12`'s
595
- mirrored-return duty. The detailed tool description proved too late to stop cold
596
- agents from searching downstream catalogs for UI; the location distinction
597
- therefore rides `initialize`, under a 1,000-character ceiling for the complete
594
+ agent chooses a route: it exists only inside `execute_code`, never in connector
595
+ search, and carries `U12`'s mirrored-return duty. The detailed call, binding,
596
+ budget, and repair rules live in the on-demand `usage` skill. The location
597
+ distinction rides `initialize`, under a 1,000-character ceiling for the complete
598
598
  instructions string. This promotes existing contract, not capability: the
599
599
  seven-tool surface, guest API, catalog, Apps delivery, and runtime do not change
600
- ([#286](https://github.com/zackbart/connecta/issues/286)).
600
+ ([#286](https://github.com/zackbart/connecta/issues/286),
601
+ [#418](https://github.com/zackbart/connecta/issues/418)).
601
602
 
602
603
  Bounded view reads follow normative [`V1`–`V8`](./program-ui-read-calls.md) ([#287](https://github.com/zackbart/connecta/issues/287), [#289](https://github.com/zackbart/connecta/issues/289)).
603
604
 
@@ -608,25 +609,22 @@ annotation-gated `maxRetries`; code mode fixes it at zero, so one
608
609
  `connecta.call` is exactly one downstream attempt. The program is the retry
609
610
  loop, and its budget is visible to it (`L4`).
610
611
 
611
- **Y2.** A program may retry a failure whose `errorDetails.retryable` is true,
612
- learned through `connecta.batch` (`S8`). Every attempt spends host-call budget,
613
- so a retry loop that ignores the budget converts a transient failure into a
614
- budget failure.
612
+ **Y2.** A program may retry a caught failure whose `retryable` is true, or a batch failure whose `errorDetails.retryable` is true (`S8`). Every attempt spends host-call budget, so an unchecked loop converts a transient failure into `budget_exceeded`.
615
613
 
616
614
  **Y3.** What must never be retried automatically:
617
615
 
618
616
  - anything with `retryable: false` — a policy refusal, a missing credential, a
619
617
  bad address, or malformed arguments will fail identically forever;
620
- - `rate_limited`, immediately. The sandbox has no timers, so a program cannot
621
- wait out a window; retrying inside it is the harm the signal exists to
622
- prevent. Return the failure and let the model, which can wait, re-issue with
623
- `retryAfterMs` in hand.
618
+ - `rate_limited`, immediately. A portable program has no timer, and a
619
+ Dynamic-Worker-only wait would spend the run's wall-clock budget on code that
620
+ fails on QuickJS. Return the failure and let the model, which can wait,
621
+ re-issue with `retryAfterMs` in hand.
624
622
  - a cancelled or timed-out *execution*: it is already over (`L1`).
625
623
 
626
624
  **Y4.** Connecta's own retry machinery beneath the meta-tools honours a
627
625
  connector-reported `Retry-After` exactly or not at all, and declines windows
628
626
  longer than 10 seconds rather than shortening them. A program sees the window
629
- verbatim as `errorDetails.retryAfterMs`.
627
+ verbatim as `err.details.retryAfterMs` or `errorDetails.retryAfterMs`.
630
628
 
631
629
  ## Cancellation and limits
632
630
 
@@ -653,13 +651,12 @@ because connecta enforces them above the sandbox:
653
651
  | Deadline per host call | 15 s |
654
652
  | Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
655
653
  | `describe` addresses | ≤ 100 |
654
+ | `describe` nearby suggestions | ≤ 3 canonical addresses per failed entry |
655
+ | Caller text echoed by `describe` recovery | ≤ 512 UTF-8 bytes per field, plus `…` |
656
656
  | Result | 24,000 serialized characters |
657
657
  | Logs presented to the model | 4,000 characters |
658
658
 
659
- Exhausting the host-call budget fails that call like any other, with code
660
- `connector_call_failed` (`E2`) and a message naming the budget — no connector was
661
- reached, so nothing more specific is true. Retrying it is pointless: the budget
662
- does not refill inside one execution.
659
+ Exhausting the host-call budget fails that call with non-retryable `budget_exceeded` (`E2`) and a message naming the budget. No connector is reached, and the budget does not refill inside one execution.
663
660
 
664
661
  **L5.** The guest is memory-, stack-, and CPU-bounded, and a program that
665
662
  exhausts a bound ends the run with an error instead of degrading the host. The
@@ -672,7 +669,7 @@ code safe to run at all.
672
669
  **L6.** A host call's serialized arguments and its serialized result are each
673
670
  bounded — QuickJS caps both at 256 KiB (`X10`) — and exceeding either fails that
674
671
  call, not the execution, so a program can catch it and ask for less. The failure
675
- is untyped text (`E1`). An over-bound *result* names the address the program
672
+ is executor-owned untyped text, not a Connecta host failure (`E1`). An over-bound *result* names the address the program
676
673
  called, not the internal dispatcher behind the shortcut namespaces; an over-bound
677
674
  *argument* payload is refused before it is parsed, so it names no address at
678
675
  all — parsing it to write a better message would spend exactly the work the bound
@@ -750,11 +747,9 @@ Worker renders arguments with `String()` (so an object logs as
750
747
  latter two. Only the three captured everywhere are contract (`R5`); rendering is
751
748
  not.
752
749
 
753
- **X5. Leftover globals.** The QuickJS guest has no `fetch`, `process`, timers,
754
- `crypto`, or `WebSocket` at all. The Dynamic Worker guest has all of them:
755
- `fetch` exists but throws on use because outbound access is disabled,
756
- `process.env` is empty, and timers work. `P2` is the contract — a program that
757
- uses `setTimeout` is writing Workers-only code, and it will fail on Node.
750
+ **X5. Leftover authority.** QuickJS blocks imports and has no `fetch`, `process`, timers, `crypto`, or `WebSocket`. A Dynamic Worker has those globals plus a non-contract set of runtime builtins through `import()` and `process.getBuiltinModule()`, including `node:path`, `node:crypto`, `node:net`, `node:tls`, `node:dns`, `node:module`, and `cloudflare:workers`. The upstream set can drift; this list is not an allowlist.
751
+ The supported Worker construction is exactly `new DynamicWorkerExecutor({ loader })`. Do not pass `bindings`, `modules`, or `globalOutbound`: each can grant ambient configuration, code, or egress. Under it, `process.env`, lexical `this.env`, and `cloudflare:workers.env` are empty; `node:fs`, `node:http`, and `node:https` are unavailable through either access route; external `fetch`, `WebSocket`, `node:net`, and `node:tls` fail with workerd's outbound-denial error; DNS lookup ends unresolved; and `fetch("data:...")` resolves locally.
752
+ `P2` is the portable contract. Programs use none of this runtime-only authority, including timers and `crypto`, because the same code fails on QuickJS. The `execute_code` description and served `usage` skill say so before an agent writes code.
758
753
 
759
754
  **X6. Stall detection.** QuickJS notices a program awaiting something that can
760
755
  never settle and fails fast; the Dynamic Worker waits for its deadline. The fast
@@ -786,11 +781,16 @@ a `process.send` with a hard ceiling. A program that returns a quarter-megabyte
786
781
  from one tool call therefore fails on Node and may succeed on Workers — reduce
787
782
  inside the program either way (`R1`).
788
783
 
784
+ **X11. Typed host rejection.** Both executors rebuild Connecta's authenticated host-failure frame as a thrown guest `Error` (`E1`). The per-run secret stays in the trusted prelude closure, and the prelude locks `globalThis.Error`, so guest code and connector prose cannot forge the host transport frame.
785
+ The human message is unchanged; a mismatched frame is ordinary untyped prose.
786
+
789
787
  ## Changes from earlier code mode
790
788
 
791
- Five behaviors changed with this contract, matching the changelog's Unreleased
789
+ Six behaviors changed with this contract, matching the changelog's Unreleased
792
790
  entry. Programs that ran before still run.
793
791
 
792
+ - **Caught Connecta failures expose their classification** (`E1`, `X11`). Their human message and thrown semantics stay unchanged; `code`, `retryable`, and `details` are additive.
793
+
794
794
  - **`connecta.batch` failures gained `errorDetails`** (`S7`). They carried only a
795
795
  message, which left a program unable to tell a policy refusal from a transient
796
796
  failure. Additive, and it reuses the host's internal batch field names, so a
@@ -833,21 +833,21 @@ the upstream `Executor` shape assignable.
833
833
  | Clauses | Test |
834
834
  | --- | --- |
835
835
  | `P1`, `P5` | `test/guest-api-contract.test.ts` (TypeScript syntax), `test/quickjs-executor.test.ts` (`normalizeCode`) |
836
- | `P2`, `X5` | `test/guest-api-contract.test.ts` (no usable network, no config) |
836
+ | `P2`, `X5` | `test/guest-api-contract.test.ts` (Dynamic globals plus loader-only filesystem, HTTP, environment, egress, DNS, and local `data:` boundaries), `test/guest-api-contract-quickjs.test.ts` (exact absent globals and blocked imports), `test/deployment-shapes.test.ts` (loader-only Worker construction) |
837
837
  | `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
838
838
  | `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
839
- | `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (sanitizing) |
839
+ | `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (sanitizing), `test/server.test.ts` (bounded live connector inventory) |
840
840
  | `A3` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (colliding alias) |
841
841
  | `A4` | `test/execute.test.ts` (namespace collisions, reserved namespace) |
842
842
  | `A5` | verdict; `A1`–`A3` are its enforcement |
843
843
  | `S1`, `S2` | `test/guest-api-contract.test.ts` (flat page, connector guides, schema keys, and the unfiltered browse that replaces `list_connectors`), `test/execute.test.ts` (guide pagination/partial/no-match behavior and `$ref`/`allOf`), `test/meta-tools.test.ts` (mixed complete/partial ranking and stable pagination) |
844
844
  | `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
845
- | `S4` | `test/guest-api-contract.test.ts` (unknown address in `describe`) |
845
+ | `S4` | both guest-contract executors (ordered mixed describe results with unknown-address, unknown-tool suggestion, and catalog-failure details), `test/meta-tools.test.ts` (top-level routing, no-suggestion, catalog-failure, and hostile-input bounds) |
846
846
  | `S5` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`) |
847
847
  | `S6` | `test/execute.test.ts` (fail-closed annotations, activity parity) |
848
848
  | `S7` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (batch cap) |
849
- | `S8`, `E1` | `test/guest-api-contract.test.ts` (typed batch outcomes) |
850
- | `E2`, `E8` | `test/guest-api-contract.test.ts` (code → `retryable`, batch and uncaught validation recovery), `test/meta-tools.test.ts` (direct, destructive, batch, provider fallback), `test/validate.test.ts` (bounded payload-free findings), `test/errors.test.ts` |
849
+ | `S8`, `E1`, `X11` | both guest-contract executors (caught call, namespace, discovery, utility, batch-validation, budget, and forgery cases; typed batch equivalence) |
850
+ | `E2`, `E8` | `test/guest-api-contract.test.ts` (code → `retryable`, caught, batch, and uncaught validation recovery), `test/meta-tools.test.ts` (direct, destructive, batch, provider fallback), `test/validate.test.ts` (bounded payload-free findings), `test/errors.test.ts` |
851
851
  | `E3` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`) |
852
852
  | `E4` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (destructive) |
853
853
  | `E5` | `test/guest-api-contract.test.ts` (execution-failure channel, in-flight `cancelled`), `test/execute.test.ts` (admission), `test/executor-admission.test.ts`, `test/quickjs-executor.test.ts` (mid-run shutdown) |
@@ -883,7 +883,7 @@ the upstream `Executor` shape assignable.
883
883
  | `U7`, `U8` | two arms passing one case table, `test/codemode-compat.test.ts` |
884
884
  | `U9` | `test/execute-ui.test.ts` (a `ui` byte aggregate distinct from `emitted`, absent when nothing was accepted) |
885
885
  | `U12` | `test/server.test.ts` (the `connecta.ui` bullet carries the return-value clause); a duty on program authors, so the description is the only place it can be enforced |
886
- | `U13` | `test/code-first-surface.test.ts`, `test/server.test.ts` (served `initialize.instructions` locate UI inside `execute_code`, exclude it from catalog search, state the one-string call and mirrored return, and stay within the complete 1,000-character budget) |
886
+ | `U13` | `test/code-first-surface.test.ts`, `test/server.test.ts` (served `initialize.instructions` locate UI inside `execute_code`, exclude it from connector search, state the mirrored-return duty, and stay within the complete 1,000-character budget; the usage skill carries detailed call rules) |
887
887
  | `X3` | `test/quickjs-executor.test.ts` (cancels a running child) |
888
888
  | `X4` | `test/guest-api-contract.test.ts` (string logs only) |
889
889
  | `X6` | `test/quickjs-executor.test.ts` (never-settling await) |
@@ -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
@@ -56,15 +60,12 @@ unknown name, unknown connector, connector with no guide — is an explicit
56
60
  error. Nothing silently falls back to the generic guide, because a generic
57
61
  answer to a specific question is worse than no answer.
58
62
 
59
- Discovery text is conditional on the deployment actually having a guide. The
60
- guide sentences in the `skills`, `search_tools`, `call_destructive_tool`, and
63
+ Discovery text is conditional on the deployment actually having a guide. Short
64
+ pointers in the `skills`, `search_tools`, `call_destructive_tool`, and
61
65
  `execute_code` descriptions appear only when at least one visible connector
62
- declares one the connector set is fixed at construction, so this is stable
63
- per deployment, and a deployment with no guides pays no always-loaded context
64
- for a feature it does not use. The built-in `usage` skill is the deliberate
65
- exception: it stays byte-identical across every deployment, including its
66
- per-connector-guides section, so an agent that has read it once in a task never
67
- needs a deployment-local copy of it.
66
+ declares one. The detailed selection rules live only in the built-in `usage`
67
+ skill. That skill stays byte-identical across deployments, including its
68
+ per-connector-guides section, so an agent reads it at most once per task.
68
69
 
69
70
  ## What belongs in a guide
70
71
 
@@ -160,15 +161,16 @@ schemas will never carry, because it cannot change them
160
161
  ## Tests that enforce this
161
162
 
162
163
  `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.
164
+ listing carrying one entry per guided connector, summaries joining a
165
+ hard-wrapped opening paragraph and shortening at readable boundaries,
166
+ configured summaries refusing construction past the bound, heading and
167
+ description fallbacks, markup skipping, whitespace-only guides treated as no
168
+ guide, content returned verbatim including surrounding padding, identical
169
+ content in two deployments staying isolated, every miss erroring rather than
170
+ falling back to the generic guide with an identically labelled skills list on
171
+ each branch, the `guide` pointer in search output, and `guideRequired`
172
+ appearing for connector-required conventions, approval-bound tools, and
173
+ truncated schemas and being absent from a search that asked for no schemas.
174
+ `test/server.test.ts` owns the conditional half: it compares a guide-free
175
+ deployment's four short pointers against a guided one's, and asserts the
176
+ complete `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
@@ -60,6 +60,14 @@ their smallest successful one-tool shapes:
60
60
 
61
61
  ## Discovery context
62
62
 
63
+ The deployment-derived `execute_code` description includes a live connector
64
+ inventory before any catalog search. It preserves registry order and uses each
65
+ canonical id, adding `shortcut <name>` only when the program namespace differs.
66
+ The complete inventory line is capped at 256 UTF-8 bytes. Entries stay whole,
67
+ and a truncated line ends with the exact `+N more` count. This reads only the
68
+ configured registry: it loads no catalog, probes no credential, grants no
69
+ capability, and does not replace canonical discovery or addressing.
70
+
63
71
  Start an unknown-address lookup with two to four distinctive action/object
64
72
  terms, not the full request, and omit `limit` so the default eight-result page
65
73
  stays small. When the integration is obvious, set `connector` to its id: a
@@ -89,14 +97,16 @@ Compact search is deliberately a routing view, not a second copy of connector
89
97
  documentation. Tool purposes are capped at 160 characters, connector
90
98
  descriptions and property prose are omitted, required input fields render
91
99
  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
100
+ bytes. Within that unchanged total, each enum node and each constraint
101
+ annotation may spend at most 256 UTF-8 bytes. Numeric bounds, string length
102
+ bounds, patterns, and formats render beside their type. A constraint that does
103
+ not fit is dropped whole. If constraints push the full shape over 1,024 bytes,
104
+ search retries the shape without them. Compact describe keeps all declared
105
+ constraints. A large enum keeps the longest whole-value prefix that fits, then
106
+ adds `unknown` and a comment with the exact omitted-value count. An empty enum
107
+ renders as the valid `never` type. A capped object becomes a valid
108
+ required-first shape with `unknown` types; other shapes become
109
+ `unknown /* truncated */`. Any cap marks the match with
100
110
  `inputSchemaTruncated` or `outputSchemaTruncated`; repeat the search with
101
111
  `includeSchemas: "json"` or use the existing describe path when exact
102
112
  constraints matter. Small enums and both exact paths remain complete.
@@ -107,12 +117,13 @@ A connector may attach a deployment-owned guide as markdown, preserving the
107
117
  original `usageGuide: string` configuration, or as
108
118
  `{ content, summary?, required? }`. The structured form does not register a
109
119
  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.
120
+ returned verbatim by `skills`; `summary` is normalized and refuses construction
121
+ when it exceeds 120 characters. When it is absent, Connecta derives the same
122
+ bounded fallback used by the skills listing: the first meaningful body
123
+ paragraph, joined across physical Markdown line wraps and shortened at a
124
+ sentence, clause, or word boundary, with a heading used only when the guide has
125
+ no body. `required: true` is reserved for generic API wrappers and
126
+ cross-operation conventions a complete downstream schema cannot express.
116
127
 
117
128
  Search and describe results keep the existing `guide: "connector:<id>"`
118
129
  pointer and add `guideSummary`. A matching tool also carries
@@ -143,8 +154,12 @@ the zero-tool page.
143
154
  The built-in `usage` skill is byte-identical across deployments and says to
144
155
  read it at most once per task. Connector guides remain scoped to the deployment
145
156
  that listed them, even when two deployments happen to use identical content.
146
- Deployments without connector guides receive none of the conditional guide
147
- sentences in their always-loaded tool descriptions.
157
+ The always-loaded instructions and seven tool definitions own route selection,
158
+ the fail-closed boundary, and the minimum guest syntax. The usage skill owns
159
+ program selection detail, examples, runtime differences, and repair guidance.
160
+ This split avoids two normative copies while preserving a valid first program
161
+ for clients that never fetch the skill. Deployments without connector guides
162
+ receive none of the short conditional guide pointers in their definitions.
148
163
 
149
164
  ## Result representation
150
165
 
@@ -170,12 +185,18 @@ can shrink anything before it returns.
170
185
 
171
186
  `fields` keeps its historical flat `{ "<path>": value }` result when every
172
187
  requested dot-path resolves. Dot notation traverses objects; append `[]` to an
173
- array field before continuing, as in `results[].id`. An exact downstream
188
+ array field before continuing, as in `results[].id`. Empty arrays resolve to
189
+ empty arrays. An exact downstream
174
190
  `$connecta` field is always escaped under `data`. If any path misses—or that
175
191
  reserved name is selected—the result carries matches under `data` and reserves `$connecta` for a
176
192
  `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
193
+ entry. A path that resolves for only some array elements stays in `data` and
194
+ appears in `partialFields`; its unresolved positions serialize as `null`, while
195
+ the recovery record distinguishes them from genuine downstream nulls. A path
196
+ that misses every element appears in `unmatchedFields` and is omitted from
197
+ `data`. Both lists scale with requested paths, never with array length. When a
198
+ miss matches a declared array path except for `[]`, the record also carries the
199
+ traversal hint. The discriminator means downstream fields
179
200
  named `data`, `projection`,
180
201
  or `$connecta` remain ordinary values nested under `data`, never apparent
181
202
  metadata. A declared output schema contributes a bounded `availableFields`
@@ -307,6 +328,16 @@ a tool. A read path that reaches an unannotated, write-capable, or destructive
307
328
  tool returns `nextAction` for `call_destructive_tool` with the canonical
308
329
  address. Nothing is executed by these records.
309
330
 
331
+ `connecta.describe` keeps failures inline so one miss cannot discard the other
332
+ schemas. Each failed entry keeps its human `error` and adds `errorDetails` with
333
+ the equivalent invocation `code` and `retryable`. Address and tool misses use
334
+ the same route-aware discovery action above. A close tool-name miss on a known
335
+ connector may also carry `suggestions`: at most three deterministically ranked
336
+ canonical addresses, with no scores or descriptions. An unknown connector
337
+ stays unscoped and has no suggestions. A catalog-load failure carries only
338
+ `code`, bounded `message`, `retryable`, and any `retryAfterMs`; discovery does
339
+ not inherit later additions to the call-failure envelope.
340
+
310
341
  That route echoes the caller's own arguments back only while they fit a
311
342
  512-byte budget, and then whole — never clipped. An error envelope is not
312
343
  size-guarded the way a result is, so an unbounded echo would let a large
@@ -384,3 +415,6 @@ duplicate `additionalProperties` branches never reach the caller. A schema the l
384
415
  validator cannot evaluate passes through to the provider. Provider error prose
385
416
  is not parsed or guessed, so an unknown format remains
386
417
  `connector_call_failed`.
418
+
419
+ Describe's nearby-address list uses the same three-item recovery bound. It
420
+ contains addresses only; it never serializes ranking scores or result prose.
@@ -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
@@ -237,6 +237,23 @@ and all deliberately absent: this is a deliberate tool surface, not a mirror of
237
237
  the API. Anything missing is reachable through a custom `api()` connector
238
238
  beside this one, which remains a first-class path.
239
239
 
240
+ The 2026-03-11 contract also offers more fields on create and update. They were
241
+ reviewed after the 0.17.0 drift check and remain deliberately absent:
242
+
243
+ - `create_page` does not create workspace-private pages, apply templates,
244
+ choose page placement, or accept expanded icon and cover forms. Those change
245
+ ownership, start asynchronous content work, control ordering, or depend on
246
+ file surfaces. They are not extensions of the maintained page/row authoring
247
+ contract (#408).
248
+ - `update_page_properties` does not lock pages, apply templates, or erase page
249
+ content. Locking is coordination state, templates finish asynchronously, and
250
+ `erase_content` permanently deletes every child block through the API. None
251
+ belongs under an approval named for property replacement (#409).
252
+
253
+ `trash_page` stays separate and reversible. The current `create_page`,
254
+ `update_page_properties`, and `trash_page` request subsets remain valid against
255
+ the expanded published contract.
256
+
240
257
  There is also **no guarded raw-REST escape hatch** — no `notion_api_get`, no
241
258
  `notion_api_mutate`. The convention that permits one
242
259
  ([H14](./provider-conventions.md#h14--a-named-tool-must-beat-the-escape-hatch-and-the-escape-hatch-splits-by-safety))