@zackbart/connecta 0.17.0 → 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.
package/dist/skills.js CHANGED
@@ -1,33 +1,70 @@
1
- export const CONNECTA_INSTRUCTIONS = 'Connecta exposes seven meta-tools. For one read at an unknown address, search_tools with 2–4 distinctive action/object terms and includeSchemas="compact", then one call_tool a lone cold call is cheaper direct than a program. For read-only reduction, multiple or dependent calls, loops, joins, or branches, do not call top-level search_tools: make one execute_code call whose program searches, selects, calls, and reduces; never return discovery for another call. connecta.ui(html) is a guest function inside execute_code, never a connector address or search_tools result; pass one HTML string for display-only, or bind named read-only refresh/drill-down calls in its optional reads argument, and return the same initial summary data the HTML renders. Unannotated, write-capable, or destructive tools stay top level: search_tools, then call_destructive_tool; authorize_connector follows auth_required; get_result follows truncation. If this routing is unfamiliar, fetch skills({ name: "usage" }).';
1
+ export const CONNECTA_INSTRUCTIONS = 'Choose a route before discovery. For one read at an unknown address, use search_tools then call_tool; a known address needs only call_tool. For read-only reduction, multiple or dependent calls, loops, joins, or branches, use one execute_code program that discovers, calls, and returns the reduced answer. Only readOnlyHint: true tools run there. Keep unannotated, write-capable, or destructive work top level: search_tools then call_destructive_tool. After auth_required use authorize_connector. After a truncated direct result use fields or get_result. connecta.ui(html) exists only inside execute_code, not in connector search; return the same summary data the HTML renders. Fetch skills({ name: "usage" }) once for program syntax, selection, repair, examples, and runtime details.';
2
2
  const USAGE_SKILL_BASE = `# Connecta usage
3
3
 
4
4
  ## The surface
5
5
 
6
6
  Seven tools: \`execute_code\`, \`search_tools\`, \`call_tool\`, \`call_destructive_tool\`, \`authorize_connector\`, \`get_result\`, \`skills\`. Broad discovery and multi-call work live in a program, not in top-level tools.
7
7
 
8
- ## Choose the smallest execution tool
8
+ The always-loaded MCP instructions are authoritative for choosing the top-level route. Read this skill at most once per task for the program workflow and recovery details below.
9
9
 
10
- Use exact addresses from discovery; never invent one. Search 2–4 distinctive action/object terms, not the whole request.
10
+ ## Inside a program
11
11
 
12
- - One read at an unknown address: \`search_tools({ query, includeSchemas: "compact" })\`, then \`call_tool\` once one cold call is cheaper direct than a program.
13
- - Anything wider — two or more calls, dependent steps, loops, joins, branching, a whole-catalog browse, or a result to reduce: one \`execute_code\` run.
14
- - Any unannotated, write-capable, or destructive call: \`call_destructive_tool\`, one at a time, after reviewing its schema and consequences.
15
- - Truncated result: retry with \`fields\`, else page it with \`get_result\`.
16
- - \`auth_required\`: \`authorize_connector\`, hand its recovery text to the operator, retry the call.
12
+ Write one plain-JavaScript async arrow function. TypeScript syntax and portable imports do not work. Return JSON-shaped data and reduce large results before returning.
17
13
 
18
- ## Inside a program
14
+ The minimum guest API is:
15
+
16
+ - \`<connectorId>.<toolName>(args)\` calls a sanitized shortcut. Non-identifier characters become \`_\`; leading digits gain \`_\`; reserved words gain a trailing \`_\`.
17
+ - \`connecta.call("connector.tool", args)\` uses the canonical address and returns the unwrapped value.
18
+ - \`connecta.search(args)\` returns \`{ tools, total, offset, limit, hasMore }\`; \`connecta.describe(args)\` returns \`{ tools }\`.
19
+ - \`connecta.batch(calls)\` runs 2–10 independent calls. Each outcome is \`{ address, ok: true, data }\` or \`{ address, ok: false, error, errorDetails }\`.
20
+ - \`console.log(...)\` is captured. \`connecta.emit(block)\` and \`connecta.ui(html, options?)\` produce rich output.
21
+
22
+ ## Discover and select
23
+
24
+ Search inside the run and finish the task there. A discovery-only program wastes a round trip. Use 2–4 distinctive action/object terms, not the full request. Use separate short searches for distinct operations.
25
+
26
+ For top-level \`search_tools\`, omit \`limit\` initially (the default is 10), then page with a limit up to 50 if needed. Empty or whitespace-only queries browse all tools. A non-empty query with no ASCII terms returns no matches; mixed input searches with its ASCII terms. \`includeSchemas: "compact"\` adds bounded input and declared output shapes. Plain objects expose \`inputKeys\`, \`requiredInputKeys\`, and \`outputKeys\`; truncation flags mark incomplete shapes; matches also carry declared annotations.
27
+
28
+ - \`connecta.search({})\` loads all catalogs. Pass \`connector: "<id>"\` when the integration is obvious. Use \`safety: "readOnly"\` for program calls. These inputs filter discovery; they grant no authority.
29
+ - Request \`includeSchemas: "compact"\`. Check address, purpose, annotations, required inputs, truncation, safety, and declared outputs. Never select only because a result ranks first or has fewer required inputs.
30
+ - Supply every \`requiredInputKey\` from the task or a prior result. For dependencies, match the earlier \`outputKey\` to the later required key. An empty required-key list does not permit invented arguments. Missing \`outputKeys\` means inspect \`outputSchema\`.
31
+ - Use \`connecta.describe({ address })\` or \`{ addresses }\` when a compact schema is truncated or insufficient. Use \`format: "json"\` only for exact constraints. Write the property names the schema displays; never guess positions or aliases.
32
+ - Reduce through declared output keys. Do not guess collection roots such as \`items\` or \`results\`. If a match or result key is missing, inspect, re-search, or describe inside the same run instead of returning discovery for another call.
33
+
34
+ Only tools explicitly annotated \`readOnlyHint: true\` are reachable. The catalog, credential, admission, and read-only gates run below the sandbox; code cannot widen its authority.
35
+
36
+ ## Errors and repair
37
+
38
+ Caught Connecta errors expose \`message\`, \`code\`, \`retryable\`, and \`details\`. Batch failures expose the same classification in \`errorDetails\`. Branch on fields, never prose. Do not retry \`retryable: false\`, and do not retry \`rate_limited\` immediately because portable code has no timer.
39
+
40
+ - \`destructive_tool_requires_approval\`: stop the program and use the returned canonical address with top-level \`call_destructive_tool\`.
41
+ - \`auth_required\`: let the failure reach the model, then use top-level \`authorize_connector\`, give its handoff to the operator, and retry after recovery.
42
+ - A truncated direct-call result: retry \`call_tool\` with \`fields\`, or follow its \`get_result\` action. A truncated program result has no page handle; filter, map, or slice inside a new program.
43
+ - Unknown addresses and tools carry scoped search recovery. Use it inside the current run. Do not invent an address.
44
+
45
+ For a direct call, \`fields\` selects JSON dot-paths and \`[]\` traverses arrays, for example \`results[].id\`. Projection misses return \`data\` plus \`$connecta\` feedback. \`resultMode: "value"\` unwraps the result. \`timeoutMs\` sets its deadline. \`maxRetries\` is honored only for safely annotated tools. \`diagnostics: true\` adds timing.
46
+
47
+ \`get_result({ id, offset?, maxBytes? })\` returns \`{ text, offset, nextOffset?, totalBytes }\` for a direct-call result. Both sizes are byte counts: \`maxBytes\` must be a whole number at least 1 and defaults to the deployment cap; \`offset\` must be a whole number at least 0 and defaults to 0. An offset inside a multi-byte character moves back to its first byte, and the response reports the served offset. Follow \`nextOffset\` to reassemble pages. An unknown or expired id is an error.
48
+
49
+ Limits: 20 host calls per run, 10 calls per batch, and a 15-second deadline per host call.
50
+
51
+ ## Runtime portability
52
+
53
+ Portable code uses only connector globals, \`connecta\`, and \`console.*\`. QuickJS blocks imports and lacks fetch, process, timers, crypto, and WebSocket. Dynamic Workers must use only \`{ loader }\`; bindings, modules, or globalOutbound grant ambient authority. With loader only, environment maps are empty; node:fs/http/https are absent; outbound fetch, WebSocket, node:net, and node:tls are denied; DNS is unresolved. Runtime builtins remain through \`import()\` and \`process.getBuiltinModule()\`, including node:path and cloudflare:workers; this set can drift. Timers, process, crypto, WebSocket, and data: fetch remain. Avoid every runtime-only capability because QuickJS fails.
54
+
55
+ ## Examples
56
+
57
+ One read-only call at a known address:
58
+
59
+ \`async () => await connecta.call("crm.get_account", { id: "acct_42" })\`
19
60
 
20
- Portable code uses only connector globals (\`<connectorId>.<toolName>(args)\`), \`connecta\`, and \`console.*\`. QuickJS blocks imports and lacks fetch/process/timers/crypto/WebSocket. Dynamic Workers require only \`{ loader }\`; bindings/modules/globalOutbound violate it. Then env maps are empty; node:fs/http/https absent; outbound fetch/WebSocket/node:net/tls denied; DNS unresolved. Runtime builtins remain through import() and process.getBuiltinModule(), including node:path and cloudflare:workers; the set can drift. Timers/process/crypto/WebSocket and data: fetch remain. Avoid them; QuickJS fails.
61
+ Dependent calls, only when the second needs a value from the first:
21
62
 
22
- - \`connecta.search({})\` loads all catalogs; pass \`connector: "<id>"\` when obvious to load one. \`safety: "readOnly"\` keeps executable calls. Neither grants authority. Matches carry \`address\` and annotations.
23
- - Exact schemas: \`connecta.describe({ address: "connector.tool" })\` for one, \`{ addresses: [...] }\` for many; \`format: "json"\` only for exact constraints.
24
- - Caught Connecta errors have \`message\`, \`code\`, \`retryable\`, and \`details\`; branch on fields. For 2–10 independent calls, \`connecta.batch([...])\` returns success data or an \`errorDetails\` whose code and retryable flag match the throw.
25
- - Search inside the run; return only the reduction the answer needs, never raw payloads.
26
- - Only tools annotated \`readOnlyHint: true\` are reachable; the gate, credentials, and admission are enforced below the sandbox — nothing a program does widens its reach.
63
+ \`async () => { const { tools } = await connecta.search({ query: "pipeline run job logs", safety: "readOnly", includeSchemas: "compact" }); const address = (suffix) => { const tool = tools.find((entry) => entry.address.endsWith(suffix)); if (!tool) throw new Error("missing " + suffix); return tool.address; }; const run = await connecta.call(address(".get_run"), { runId: 42 }); const logs = await connecta.call(address(".get_job_logs"), { jobId: run.failedJobId }); return logs.map(({ timestamp, message }) => ({ timestamp, message })); }\`
27
64
 
28
65
  ## Rendering a view
29
66
 
30
- \`connecta.ui(html)\` renders one success-only display view, never for the model. Fetch and check the shape first. On empty or missing data, return a trimmed first record instead of rendering. Otherwise render returned variables; the model reads the return value, not the view.
67
+ \`connecta.emit\` accepts text, image, or audio blocks and delivers them only on success. \`connecta.ui(html)\` renders one success-only display view outside model context. One argument is display-only. Bind read-only refresh or drill-down calls with \`{ reads: { name: { address, fixedArgs?, viewArgs? } } }\`; page markup calls \`connecta.read(name, args)\`. Admission and one shared budget apply to the UI and emitted content, not separate budgets. Fetch and check the data shape first. On empty or missing data, return a trimmed first record instead of rendering. Otherwise render returned variables and return the same initial summary because the model reads the return value, not the view. A second, invalid, or over-budget UI call throws catchably.
31
68
 
32
69
  `;
33
70
  /** Deployment-scoped guide routing appended to the shared usage guide. */
package/dist/version.d.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * a bump that forgets this file fails the build rather than shipping a stale
5
5
  * version to `/health` and to downstream MCP handshakes.
6
6
  */
7
- export declare const CONNECTA_VERSION = "0.17.0";
7
+ export declare const CONNECTA_VERSION = "0.18.0";
package/dist/version.js CHANGED
@@ -4,4 +4,4 @@
4
4
  * a bump that forgets this file fails the build rather than shipping a stale
5
5
  * version to `/health` and to downstream MCP handshakes.
6
6
  */
7
- export const CONNECTA_VERSION = "0.17.0";
7
+ export const CONNECTA_VERSION = "0.18.0";
@@ -185,7 +185,7 @@ src/
185
185
  | The core imports no `node:` builtin and reaches no Node-only module | `test/purity.test.ts` |
186
186
  | The published surface matches the same boundary | `test/package-surface.test.ts`, `scripts/check-package.mjs` |
187
187
  | Route order, per-route auth, and byte-exact refusals | `test/server-route-contracts.test.ts` |
188
- | `/mcp` end to end, the open routes, exactly seven tools | `test/server.test.ts`, `test/code-first-surface.test.ts` |
188
+ | `/mcp` end to end, the open routes, exactly seven tools, bounded connector orientation | `test/server.test.ts`, `test/code-first-surface.test.ts` |
189
189
  | Construction-time refusals and the grouped config boundary | `test/config.test.ts`, `test/registry.test.ts` |
190
190
  | Program and top-level calls take the same enforced path | `test/execute.test.ts` |
191
191
  | Both deployment shapes still compile and configure the real thing | `test/deployment-shapes.test.ts`, `npm run check:examples` |
@@ -173,7 +173,10 @@ global whose properties are its tools, so `<connectorId>.<toolName>(args)` works
173
173
  with both parts sanitized into JavaScript identifiers — characters outside
174
174
  `[A-Za-z0-9_$]` become `_`, a leading digit gets `_` prefixed, and a reserved
175
175
  word gets `_` appended (`my-service.get.thing` → `my_service.get_thing`). The
176
- 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.
177
180
 
178
181
  **A3.** A shortcut that resolves to more than one tool fails closed with
179
182
  `ambiguous_tool_alias`, naming the colliding tool names and pointing at
@@ -256,10 +259,13 @@ const { tools } = await connecta.describe({
256
259
  });
257
260
  ```
258
261
 
259
- **S4.** Returns `{ tools }` in the order asked, one entry per address. An
260
- address that is unknown, or whose connector's catalog could not be loaded,
261
- returns an entry carrying `error` one bad address never fails the whole call.
262
- 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.
263
269
 
264
270
  ### connecta.call
265
271
 
@@ -377,7 +383,7 @@ exactly first, by containment second — so a program that *wraps* a failure's
377
383
  message in its own text still reports the underlying typed failure. Keeping the
378
384
  type beats keeping the prose.
379
385
 
380
- **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.
381
387
 
382
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`.
383
389
 
@@ -585,14 +591,14 @@ the same mistake as automatic host-side projection, refused in `ethos.md`
585
591
  ([#282](https://github.com/zackbart/connecta/issues/282)).
586
592
 
587
593
  **U13.** The always-loaded MCP instructions locate `connecta.ui(html)` before an
588
- agent chooses a route: it is a guest function inside `execute_code`, never a
589
- connector address or catalog result, takes one HTML string, and carries `U12`'s
590
- mirrored-return duty. The detailed tool description proved too late to stop cold
591
- agents from searching downstream catalogs for UI; the location distinction
592
- 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
593
598
  instructions string. This promotes existing contract, not capability: the
594
599
  seven-tool surface, guest API, catalog, Apps delivery, and runtime do not change
595
- ([#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)).
596
602
 
597
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)).
598
604
 
@@ -645,6 +651,8 @@ because connecta enforces them above the sandbox:
645
651
  | Deadline per host call | 15 s |
646
652
  | Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
647
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 `…` |
648
656
  | Result | 24,000 serialized characters |
649
657
  | Logs presented to the model | 4,000 characters |
650
658
 
@@ -828,13 +836,13 @@ the upstream `Executor` shape assignable.
828
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) |
829
837
  | `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
830
838
  | `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
831
- | `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) |
832
840
  | `A3` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (colliding alias) |
833
841
  | `A4` | `test/execute.test.ts` (namespace collisions, reserved namespace) |
834
842
  | `A5` | verdict; `A1`–`A3` are its enforcement |
835
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) |
836
844
  | `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
837
- | `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) |
838
846
  | `S5` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`) |
839
847
  | `S6` | `test/execute.test.ts` (fail-closed annotations, activity parity) |
840
848
  | `S7` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (batch cap) |
@@ -875,7 +883,7 @@ the upstream `Executor` shape assignable.
875
883
  | `U7`, `U8` | two arms passing one case table, `test/codemode-compat.test.ts` |
876
884
  | `U9` | `test/execute-ui.test.ts` (a `ui` byte aggregate distinct from `emitted`, absent when nothing was accepted) |
877
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 |
878
- | `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) |
879
887
  | `X3` | `test/quickjs-executor.test.ts` (cancels a running child) |
880
888
  | `X4` | `test/guest-api-contract.test.ts` (string logs only) |
881
889
  | `X6` | `test/quickjs-executor.test.ts` (never-settling await) |
@@ -60,15 +60,12 @@ unknown name, unknown connector, connector with no guide — is an explicit
60
60
  error. Nothing silently falls back to the generic guide, because a generic
61
61
  answer to a specific question is worse than no answer.
62
62
 
63
- Discovery text is conditional on the deployment actually having a guide. The
64
- 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
65
65
  `execute_code` descriptions appear only when at least one visible connector
66
- declares one the connector set is fixed at construction, so this is stable
67
- per deployment, and a deployment with no guides pays no always-loaded context
68
- for a feature it does not use. The built-in `usage` skill is the deliberate
69
- exception: it stays byte-identical across every deployment, including its
70
- per-connector-guides section, so an agent that has read it once in a task never
71
- 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.
72
69
 
73
70
  ## What belongs in a guide
74
71
 
@@ -175,5 +172,5 @@ each branch, the `guide` pointer in search output, and `guideRequired`
175
172
  appearing for connector-required conventions, approval-bound tools, and
176
173
  truncated schemas — and being absent from a search that asked for no schemas.
177
174
  `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.
175
+ deployment's four short pointers against a guided one's, and asserts the
176
+ complete `usage` skill is byte-identical between them.
@@ -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
@@ -146,8 +154,12 @@ the zero-tool page.
146
154
  The built-in `usage` skill is byte-identical across deployments and says to
147
155
  read it at most once per task. Connector guides remain scoped to the deployment
148
156
  that listed them, even when two deployments happen to use identical content.
149
- Deployments without connector guides receive none of the conditional guide
150
- 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.
151
163
 
152
164
  ## Result representation
153
165
 
@@ -316,6 +328,16 @@ a tool. A read path that reaches an unannotated, write-capable, or destructive
316
328
  tool returns `nextAction` for `call_destructive_tool` with the canonical
317
329
  address. Nothing is executed by these records.
318
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
+
319
341
  That route echoes the caller's own arguments back only while they fit a
320
342
  512-byte budget, and then whole — never clipped. An error envelope is not
321
343
  size-guarded the way a result is, so an unbounded echo would let a large
@@ -393,3 +415,6 @@ duplicate `additionalProperties` branches never reach the caller. A schema the l
393
415
  validator cannot evaluate passes through to the provider. Provider error prose
394
416
  is not parsed or guessed, so an unknown format remains
395
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.
@@ -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))
@@ -183,7 +183,7 @@ Two more runners are deliberately outside `check`:
183
183
 
184
184
  - `npm run test:browser` — Playwright against a real headless Chromium
185
185
  (`npm run test:browser:install` once). It covers the embedded bundle without
186
- adding a browser download to both CI Node-version jobs.
186
+ adding a browser download to the CI release check.
187
187
  - `npm run drift:check` — the maintainer-run provider drift check, with local
188
188
  provider credentials exported. No credential goes near CI and nothing files
189
189
  itself; findings are read by a human and become issues
@@ -225,23 +225,23 @@ in.
225
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 |
226
226
  | `cloudflare-provider.test.ts` | `cloudflare()` construction, tool surface, request building, projections, typed failures, and credential test |
227
227
  | `cloudflare-registry.test.ts` | the same provider inside a real deployment: discovery including compact page bounds, addressing, and admission through the registry |
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 |
228
+ | `code-first-surface.test.ts` | the seven-tool surface itself — an executor required, every removed option and top-level tool refused, compact always-loaded routing pinned below 1,000 characters, complete on-demand usage served, and `connecta.ui` findable before connector search |
229
229
  | `codemode-compat.test.ts` | the `Executor` seam staying structurally compatible with `@cloudflare/codemode`'s `DynamicWorkerExecutor`, enforced by `tsc` |
230
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 |
231
231
  | `credentials.test.ts` | the pure stored-shape classifier (containment, not equality) and the AES-GCM vault: round-trip, ciphertext bound to its connector id, named field sets, masked metadata, wrong-key rejection, deletion, coexistence with OAuth keys |
232
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 |
233
233
  | `downstream-oauth.test.ts` | `KvOAuthProvider` round-trips and races, `auth_required` versus `error`, `startAuth`/`finishAuth`, callback refusal equality, bounded diagnostics, and HTML escaping |
234
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 |
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 |
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, MCP/code-mode invocation parity, and payload-free describe diagnostics |
236
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 |
237
237
  | `execute-ui.test.ts` | `connecta.ui` (U1–U9) — validation, multiplicity and budget, the provider, `_meta` delivery, and the Apps shell |
238
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 |
239
239
  | `guarded-fetch.test.ts` | the guarded transport — construction, request building, destination confinement, and response handling |
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 |
240
+ | `guest-api-contract.test.ts` | the shared guest contract on the Dynamic Worker, including caught call, typed inline describe recovery, 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 |
241
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 |
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 |
242
+ | `meta-tools.test.ts` | the registry-backed meta-tools: bounded discovery with page and address maxima, compact descriptions and the complete on-demand usage skill, compact and JSON schemas with constraints, typed describe recovery and suggestions, structured errors, connector-guide selection and summary bounds, stored-credential drift, catalog health, field projection, truncation and `get_result`, per-connector result bounds, probe timeouts, and unavailable or unknown browse recovery |
243
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 |
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 |
244
+ | `notion-provider.test.ts` / `notion-registry.test.ts` | Notion's deliberate tool surface, including declined expanded page inputs, request construction, lean projections, both pagination conventions, error mapping, and writes; then the connector in a real deployment |
245
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 |
246
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 |
247
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) |
@@ -249,10 +249,10 @@ in.
249
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 |
250
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 |
251
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 |
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` |
252
+ | `server.test.ts` | end-to-end `/mcp` (401 → compact initialize instructions → seven compact definitions with bounded connector inventory complete usage skill → `call_tool`), conditional guide pointers, open routes, Clerk `.well-known` metadata without network, code mode, and deferred catalog reads through both discovery surfaces |
253
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 |
254
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` |
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 |
255
+ | `stripe-provider.test.ts` / `stripe-registry.test.ts` | the Stripe proxy's mixed-mode OAuth and fixed-mode header contracts, admission, exact account selectors, and no-guess rule; then fixed credentials in a real deployment |
256
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 |
257
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 |
258
258
 
@@ -263,12 +263,12 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
263
263
 
264
264
  | Suite | Covers | Why Node |
265
265
  | --- | --- | --- |
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 |
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 Node template's pinned esbuild install-script approval, 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 |
267
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 |
268
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 |
269
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 |
270
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 |
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 |
271
+ | `guest-api-contract-quickjs.test.ts` | the shared guest-contract cases on the real QuickJS executor, including identical caught failure codes and inline describe recovery, its exact absent globals, and blocked runtime imports | runs the contract cases on the Node QuickJS executor |
272
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 |
273
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 |
274
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 |
@@ -81,6 +81,14 @@ deliberate surface.
81
81
  | H13 guide | meets | structured, declared summary, `required: true` with a stated reason — the database→data-source lookup is a sequence no complete schema can express |
82
82
  | H14 hatch | **missed → fixed** | Notion has no guarded raw-REST tool, which H14 explicitly permits for a finite surface — provided it says so. It did not. The guide now names the absence, so an agent does not spend a search proving there is no `notion_api_get` |
83
83
 
84
+ The 0.17.0 drift review also considered Notion's expanded create and update
85
+ contracts. Workspace-private creation, templates, placement, richer media,
86
+ locking, and irreversible content erasure stay outside the maintained surface.
87
+ They are separate ownership, ordering, asynchronous, coordination, file, or
88
+ deletion workflows rather than missing fields on the five existing writes
89
+ ([#408](https://github.com/zackbart/connecta/issues/408),
90
+ [#409](https://github.com/zackbart/connecta/issues/409)).
91
+
84
92
  ## Linear — hosted-MCP proxy
85
93
 
86
94
  | Convention | Verdict | Notes |
@@ -105,16 +113,16 @@ deliberate surface.
105
113
  | --- | --- | --- |
106
114
  | P1 add, never rewrite | meets | annotations only |
107
115
  | 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
- | P3 routing fact | meets | production versus sandbox appears in the title, the description, and the guide's first line |
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 |
116
+ | P3 routing fact | meets | OAuth metadata states mixed account scope and the guide resolves mode from `livemode`; fixed header credentials retain their mode in every routing surface |
117
+ | P4 endpoint default | meets | OAuth has no connector-wide mode to default; static headers require one, and construction throws when a recognizable key prefix contradicts it |
110
118
  | 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
119
  | 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
- | 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 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)) |
120
+ | P7 reduction advice | **missed → fixed** | OAuth has a mixed-scope summary; fixed credentials keep mode-shaped summaries. `required` stays unset because the four generic tools remain the routing decision |
121
+ | P8 identity resolution | **missed → fixed** | The guide names typed object ids and their read sources. For OAuth it requires `list_available_accounts_or_orgs`, then carries the returned `stripe_context` and `livemode` unchanged; ambiguity stops ([#404](https://github.com/zackbart/connecta/issues/404), [#414](https://github.com/zackbart/connecta/issues/414)) |
114
122
  | 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
123
  | 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
124
  | 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
- | 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 |
125
+ | P12 admission budget | meets | fixed credentials use their documented mode rate; mixed OAuth uses the stricter 25/s sandbox rate and concurrency bound |
118
126
  | P13 drift visible | meets | both lists are module-level constants in one file, and are the manifest the refresh-time drift check compares against ([#343](https://github.com/zackbart/connecta/issues/343)) |
119
127
 
120
128
  ## Mixpanel — hosted-MCP proxy