@zackbart/connecta 0.16.0 → 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 (51) hide show
  1. package/AGENTS.md +12 -5
  2. package/CHANGELOG.md +289 -0
  3. package/README.md +6 -1
  4. package/dist/catalog-service.d.ts +4 -0
  5. package/dist/catalog-service.js +49 -5
  6. package/dist/catalog.d.ts +11 -0
  7. package/dist/catalog.js +134 -12
  8. package/dist/errors.d.ts +28 -2
  9. package/dist/errors.js +1 -0
  10. package/dist/execute.d.ts +5 -0
  11. package/dist/execute.js +229 -161
  12. package/dist/invocation.js +3 -1
  13. package/dist/meta-tools.d.ts +4 -0
  14. package/dist/meta-tools.js +46 -14
  15. package/dist/operator-ui/generated.d.ts +1 -1
  16. package/dist/operator-ui/generated.js +1 -1
  17. package/dist/operator-ui/model.d.ts +3 -1
  18. package/dist/providers/cloudflare.js +13 -25
  19. package/dist/providers/mixpanel.d.ts +3 -5
  20. package/dist/providers/mixpanel.js +73 -5
  21. package/dist/providers/stripe.d.ts +2 -2
  22. package/dist/providers/stripe.js +13 -11
  23. package/dist/registry.d.ts +32 -9
  24. package/dist/registry.js +217 -33
  25. package/dist/routes/mcp.js +6 -0
  26. package/dist/skills.d.ts +4 -0
  27. package/dist/skills.js +157 -18
  28. package/dist/types.d.ts +14 -2
  29. package/dist/ui.js +4 -1
  30. package/dist/version.d.ts +1 -1
  31. package/dist/version.js +1 -1
  32. package/documentation/architecture.md +7 -4
  33. package/documentation/cloudflare.md +40 -8
  34. package/documentation/code-first-exploration.md +2 -2
  35. package/documentation/code-mode.md +45 -53
  36. package/documentation/connector-guides.md +24 -19
  37. package/documentation/connectors.md +13 -1
  38. package/documentation/meta-tools.md +33 -18
  39. package/documentation/mixpanel.md +20 -0
  40. package/documentation/notion.md +7 -2
  41. package/documentation/operations.md +74 -29
  42. package/documentation/operator-ui.md +12 -2
  43. package/documentation/provider-audit.md +4 -4
  44. package/documentation/provider-conventions.md +68 -19
  45. package/documentation/stripe.md +45 -14
  46. package/documentation/upgrading.md +478 -0
  47. package/ethos.md +4 -4
  48. package/examples/worker/README.md +13 -6
  49. package/package.json +7 -2
  50. package/templates/node/AGENTS.md +5 -0
  51. package/templates/node/package.json +1 -1
@@ -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
 
@@ -240,8 +241,8 @@ can set `includeSchemaKeys: false` to buy the bytes back.
240
241
  **S3.** Discovery is bounded and the bounds throw rather than silently shrink: a
241
242
  `limit` outside 1–100 is `invalid_args`, and a page whose serialized form
242
243
  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.
244
+ ask for less. The thrown error carries the stable `code`, `retryable`, and
245
+ `details` fields (`E1`).
245
246
 
246
247
  ### connecta.describe
247
248
 
@@ -295,9 +296,7 @@ order. A success is `{ address, ok: true, data }`. A failure is
295
296
  field names the host's internal batch path uses. One failing call never rejects
296
297
  the batch, and more than ten calls throws.
297
298
 
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.
299
+ **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
300
 
302
301
  ### connecta.emit
303
302
 
@@ -310,27 +309,20 @@ clauses are [Emitted output](#emitted-output) (`M1`–`M10`).
310
309
 
311
310
  ## Errors
312
311
 
313
- **E1.** There are four error channels, and only two of them are typed.
312
+ **E1.** There are four error channels. Connecta failures are typed whether caught or uncaught.
314
313
 
315
314
  | Channel | Shape | Typed? |
316
315
  | --- | --- | --- |
317
- | A throw inside the program | `Error` with `message` only | no |
316
+ | A caught Connecta host failure | `Error` with `message`, `code`, `retryable`, and `details` | yes |
318
317
  | `connecta.batch` outcome | `{ ok: false, error, errorDetails }` | yes |
319
318
  | 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 |
319
+ | Program or execution failure (`E5`, `E6`, a bridge bound in `L6`) | error text | no |
321
320
 
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.
321
+ 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.
322
+ `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.
323
+ Program-authored errors stay untyped, and code must never parse error prose.
331
324
 
332
- **E2.** The taxonomy. `retryable` is what connecta reports; `Y3` says what a
333
- program may do about it.
325
+ **E2.** The taxonomy: `retryable` is what connecta reports, `Y3` what a program may do.
334
326
 
335
327
  | Code | Raised when | `retryable` |
336
328
  | --- | --- | --- |
@@ -340,15 +332,18 @@ program may do about it.
340
332
  | `destructive_tool_requires_approval` | the tool is not explicitly read-only | false |
341
333
  | `auth_required` | the credential is missing, expired, or rejected | false |
342
334
  | `invalid_args` | arguments or discovery bounds were rejected | false |
335
+ | `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
336
  | `input_required_unsupported` | a downstream asked for mid-call input | false |
344
337
  | `rate_limited` | the downstream reported a rate limit | true |
345
338
  | `unavailable` | the downstream is down or unreachable | true |
346
339
  | `timeout` | the per-call 15-second deadline expired | true |
347
340
  | `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 |
341
+ | `connector_call_failed` | anything else the connector threw | per message |
349
342
  | `batch_call_failed` | a `connecta.batch` entry connecta could not even attempt | per message |
350
343
  | `catalog_lookup_failed` | the connector's catalog could not be loaded | per cause |
351
344
  | `result_processing_failed` | the result could not be prepared | per message |
345
+ | `result_too_large` | a discovery response exceeded its byte bound | false |
346
+ | `budget_exceeded` | the run exhausted a host-call or emitted-output budget | false |
352
347
 
353
348
  **E3.** `auth_required` carries the same recovery envelope as `call_tool`:
354
349
  `connector`, `operation`, `recovery` (`oauth`, `operator_config`, or
@@ -608,25 +603,22 @@ annotation-gated `maxRetries`; code mode fixes it at zero, so one
608
603
  `connecta.call` is exactly one downstream attempt. The program is the retry
609
604
  loop, and its budget is visible to it (`L4`).
610
605
 
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.
606
+ **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
607
 
616
608
  **Y3.** What must never be retried automatically:
617
609
 
618
610
  - anything with `retryable: false` — a policy refusal, a missing credential, a
619
611
  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.
612
+ - `rate_limited`, immediately. A portable program has no timer, and a
613
+ Dynamic-Worker-only wait would spend the run's wall-clock budget on code that
614
+ fails on QuickJS. Return the failure and let the model, which can wait,
615
+ re-issue with `retryAfterMs` in hand.
624
616
  - a cancelled or timed-out *execution*: it is already over (`L1`).
625
617
 
626
618
  **Y4.** Connecta's own retry machinery beneath the meta-tools honours a
627
619
  connector-reported `Retry-After` exactly or not at all, and declines windows
628
620
  longer than 10 seconds rather than shortening them. A program sees the window
629
- verbatim as `errorDetails.retryAfterMs`.
621
+ verbatim as `err.details.retryAfterMs` or `errorDetails.retryAfterMs`.
630
622
 
631
623
  ## Cancellation and limits
632
624
 
@@ -656,10 +648,7 @@ because connecta enforces them above the sandbox:
656
648
  | Result | 24,000 serialized characters |
657
649
  | Logs presented to the model | 4,000 characters |
658
650
 
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.
651
+ 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
652
 
664
653
  **L5.** The guest is memory-, stack-, and CPU-bounded, and a program that
665
654
  exhausts a bound ends the run with an error instead of degrading the host. The
@@ -672,7 +661,7 @@ code safe to run at all.
672
661
  **L6.** A host call's serialized arguments and its serialized result are each
673
662
  bounded — QuickJS caps both at 256 KiB (`X10`) — and exceeding either fails that
674
663
  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
664
+ is executor-owned untyped text, not a Connecta host failure (`E1`). An over-bound *result* names the address the program
676
665
  called, not the internal dispatcher behind the shortcut namespaces; an over-bound
677
666
  *argument* payload is refused before it is parsed, so it names no address at
678
667
  all — parsing it to write a better message would spend exactly the work the bound
@@ -750,11 +739,9 @@ Worker renders arguments with `String()` (so an object logs as
750
739
  latter two. Only the three captured everywhere are contract (`R5`); rendering is
751
740
  not.
752
741
 
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.
742
+ **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.
743
+ 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.
744
+ `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
745
 
759
746
  **X6. Stall detection.** QuickJS notices a program awaiting something that can
760
747
  never settle and fails fast; the Dynamic Worker waits for its deadline. The fast
@@ -786,11 +773,16 @@ a `process.send` with a hard ceiling. A program that returns a quarter-megabyte
786
773
  from one tool call therefore fails on Node and may succeed on Workers — reduce
787
774
  inside the program either way (`R1`).
788
775
 
776
+ **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.
777
+ The human message is unchanged; a mismatched frame is ordinary untyped prose.
778
+
789
779
  ## Changes from earlier code mode
790
780
 
791
- Five behaviors changed with this contract, matching the changelog's Unreleased
781
+ Six behaviors changed with this contract, matching the changelog's Unreleased
792
782
  entry. Programs that ran before still run.
793
783
 
784
+ - **Caught Connecta failures expose their classification** (`E1`, `X11`). Their human message and thrown semantics stay unchanged; `code`, `retryable`, and `details` are additive.
785
+
794
786
  - **`connecta.batch` failures gained `errorDetails`** (`S7`). They carried only a
795
787
  message, which left a program unable to tell a policy refusal from a transient
796
788
  failure. Additive, and it reuses the host's internal batch field names, so a
@@ -833,7 +825,7 @@ the upstream `Executor` shape assignable.
833
825
  | Clauses | Test |
834
826
  | --- | --- |
835
827
  | `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) |
828
+ | `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
829
  | `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
838
830
  | `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
839
831
  | `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (sanitizing) |
@@ -846,8 +838,8 @@ the upstream `Executor` shape assignable.
846
838
  | `S5` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`) |
847
839
  | `S6` | `test/execute.test.ts` (fail-closed annotations, activity parity) |
848
840
  | `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` |
841
+ | `S8`, `E1`, `X11` | both guest-contract executors (caught call, namespace, discovery, utility, batch-validation, budget, and forgery cases; typed batch equivalence) |
842
+ | `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
843
  | `E3` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`) |
852
844
  | `E4` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (destructive) |
853
845
  | `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) |
@@ -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`
@@ -229,7 +238,13 @@ results explain that no single tool covered every term and recommend splitting
229
238
  distinct intents. A true negative says that no matching capability is
230
239
  configured and recommends refining, connector-scoping, or browsing; when a
231
240
  connector catalog was unavailable, the response includes
232
- `unavailableConnectorCount` instead of making that stronger claim. A search
241
+ `unavailableConnectorCount` instead of making that stronger claim. A no-match
242
+ query whose terms name a configured connector's `id` or `title` never makes it
243
+ either: connector identity is not in the lexical index — indexing it would move
244
+ ranking for every query that already matches tools — so instead the guidance on
245
+ an unscoped miss names up to three such connectors by ID and sends the caller
246
+ to a scoped browse. Identity affects that one sentence and nothing else: no
247
+ ranking, no result, and no new field. A search
233
248
  explicitly scoped to that unavailable connector also receives `catalogError` —
234
249
  the bounded classified failure (`code`, `message`, `retryable`, and any
235
250
  `retryAfterMs`) so the caller can tell a transient outage from one a deployment
@@ -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
@@ -166,7 +166,7 @@ should do next, and two of Notion's are easy to mistranslate.
166
166
  | 400 (`validation_error`, `invalid_json`, `invalid_request`, `missing_version`, …) | `invalid_args` | every documented 400 is a malformed request |
167
167
  | 401 `unauthorized` | `auth_required` | the token is missing or invalid |
168
168
  | 403 `restricted_resource` | `connector_call_failed`, non-retryable | **not** `auth_required` |
169
- | 404 `object_not_found` | `connector_call_failed`, non-retryable | overloaded; see below |
169
+ | 404 `object_not_found` | `connector_call_failed`, non-retryable | overloaded — deliberately **not** `not_found`; see below |
170
170
  | 409 `conflict_error` | `unavailable`, retryable | Notion says to retry |
171
171
  | 429 `rate_limited` | `rate_limited` + `retryAfterMs` | `Retry-After` seconds → ms |
172
172
  | 529 `service_overload` | `unavailable` + `retryAfterMs` | back off like a 429 |
@@ -184,7 +184,12 @@ whose message says an operator must change it in Notion.
184
184
  object that does not exist and for one that exists but has not been shared with
185
185
  the integration, and it will not say which. The message says both, because
186
186
  treating it as deletion is exactly how an agent concludes a page is gone when
187
- it was simply never shared.
187
+ it was simply never shared. This is why the row above does not use `not_found`,
188
+ which exists precisely to say "it is not there": the qualifier on that code
189
+ ([H11](./provider-conventions.md#h11--errors-are-mapped-to-what-the-caller-does-next))
190
+ is that the provider must tell absence apart from a permission gap, and Notion
191
+ does not. A program that skipped this id as missing would be right about half
192
+ the time, which is the half that matters.
188
193
 
189
194
  ## Rate limiting
190
195