@zackbart/connecta 0.28.0 → 0.29.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 (320) hide show
  1. package/CHANGELOG.md +961 -5
  2. package/README.md +62 -39
  3. package/bin/connecta.mjs +110 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1632 -1509
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +526 -0
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +220 -0
  52. package/dist/connectors/api-connector.js +214 -0
  53. package/dist/connectors/api.d.ts +8 -71
  54. package/dist/connectors/api.js +13 -99
  55. package/dist/connectors/guarded-fetch.d.ts +144 -0
  56. package/dist/connectors/guarded-fetch.js +108 -25
  57. package/dist/connectors/negotiation-cache.js +46 -0
  58. package/dist/connectors/option-shapes.js +27 -0
  59. package/dist/connectors/remote-mcp.d.ts +30 -4
  60. package/dist/connectors/remote-mcp.js +1780 -351
  61. package/dist/connectors/resource-uri.js +335 -0
  62. package/dist/credential-contract.d.ts +10 -0
  63. package/dist/credential-rules.js +1 -3
  64. package/dist/credential-url.js +108 -0
  65. package/dist/credentials.d.ts +6 -1
  66. package/dist/credentials.js +27 -18
  67. package/dist/d1.d.ts +130 -0
  68. package/dist/d1.js +290 -0
  69. package/dist/describe-config.d.ts +165 -0
  70. package/dist/describe-config.js +315 -0
  71. package/dist/described.js +106 -0
  72. package/dist/downstream-input-context.js +119 -0
  73. package/dist/downstream-input.js +323 -0
  74. package/dist/errors.d.ts +97 -20
  75. package/dist/errors.js +158 -80
  76. package/dist/execute.js +453 -305
  77. package/dist/executor-admission.d.ts +1 -0
  78. package/dist/executor-admission.js +11 -15
  79. package/dist/executor-contract.d.ts +20 -0
  80. package/dist/executor-contract.js +48 -0
  81. package/dist/executor-result.js +24 -9
  82. package/dist/executors/quickjs-child.js +3 -4
  83. package/dist/executors/quickjs-runtime.js +147 -59
  84. package/dist/executors/quickjs.js +29 -35
  85. package/dist/favicon.js +1 -2
  86. package/dist/fix-prompt.js +1 -3
  87. package/dist/guest-runtime.js +188 -0
  88. package/dist/guest-types.d.ts +176 -0
  89. package/dist/guest-types.js +1 -0
  90. package/dist/html-security.js +24 -0
  91. package/dist/identity.js +1 -3
  92. package/dist/inbound-credential.js +52 -0
  93. package/dist/index.d.ts +25 -257
  94. package/dist/index.js +65 -478
  95. package/dist/invocation-auth.js +69 -0
  96. package/dist/invocation.js +230 -73
  97. package/dist/json-schema.d.ts +1 -1
  98. package/dist/mcp-client-context.js +35 -0
  99. package/dist/mcp-result.js +11 -7
  100. package/dist/meta-output.js +76 -0
  101. package/dist/meta-tool-names.d.ts +2 -0
  102. package/dist/meta-tool-names.js +3 -0
  103. package/dist/meta-tools.js +285 -209
  104. package/dist/module-contracts.d.ts +8 -23
  105. package/dist/node.d.ts +0 -2
  106. package/dist/node.js +2 -2
  107. package/dist/oauth-callback-outcome.js +1 -3
  108. package/dist/oauth-handoff.js +117 -0
  109. package/dist/oauth-partition.js +17 -0
  110. package/dist/oauth-sealing.js +20 -0
  111. package/dist/operator-record.d.ts +137 -0
  112. package/dist/operator-record.js +420 -0
  113. package/dist/operator-ui/assets.js +31 -0
  114. package/dist/operator-ui/contract.d.ts +72 -0
  115. package/dist/operator-ui/contract.js +1 -0
  116. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  117. package/dist/operator-ui/generated.js +5 -4
  118. package/dist/operator-ui/model.d.ts +12 -87
  119. package/dist/operator-ui/model.js +2 -9
  120. package/dist/operator-ui/setup-commands.js +1 -3
  121. package/dist/operator-ui/view.d.ts +323 -0
  122. package/dist/operator-ui/view.js +62 -189
  123. package/dist/page-styles.js +2 -3
  124. package/dist/program-source.js +14 -2
  125. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  126. package/dist/provider.d.ts +142 -0
  127. package/dist/provider.js +187 -0
  128. package/dist/providers/_shared/google/result-size.js +57 -0
  129. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  130. package/dist/providers/_shared/google/service-account.js +500 -0
  131. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  132. package/dist/providers/_shared/google/workspace.js +518 -0
  133. package/dist/providers/basecamp/index.d.ts +43 -0
  134. package/dist/providers/basecamp/index.js +997 -0
  135. package/dist/providers/basecamp/skill.generated.js +9 -0
  136. package/dist/providers/breeze/index.d.ts +34 -0
  137. package/dist/providers/breeze/index.js +1446 -0
  138. package/dist/providers/breeze/skill.generated.js +9 -0
  139. package/dist/providers/ccb/index.d.ts +103 -0
  140. package/dist/providers/ccb/index.js +1725 -0
  141. package/dist/providers/ccb/skill.generated.js +9 -0
  142. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  143. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  144. package/dist/providers/cloudflare/skill.generated.js +10 -0
  145. package/dist/providers/docs/index.d.ts +54 -0
  146. package/dist/providers/docs/index.js +1237 -0
  147. package/dist/providers/docs/skill.generated.js +9 -0
  148. package/dist/providers/drive/index.d.ts +53 -0
  149. package/dist/providers/drive/index.js +1523 -0
  150. package/dist/providers/drive/skill.generated.js +9 -0
  151. package/dist/providers/forms/index.d.ts +54 -0
  152. package/dist/providers/forms/index.js +1217 -0
  153. package/dist/providers/forms/skill.generated.js +11 -0
  154. package/dist/providers/github/auth.d.ts +32 -0
  155. package/dist/providers/github/auth.js +361 -0
  156. package/dist/providers/github/hosted.js +218 -0
  157. package/dist/providers/github/index.d.ts +13 -0
  158. package/dist/providers/github/index.js +140 -0
  159. package/dist/providers/github/key.js +100 -0
  160. package/dist/providers/github/rest.js +321 -0
  161. package/dist/providers/github/scope.d.ts +29 -0
  162. package/dist/providers/github/scope.js +124 -0
  163. package/dist/providers/github/skill.generated.js +8 -0
  164. package/dist/providers/gmail/index.d.ts +51 -0
  165. package/dist/providers/gmail/index.js +1436 -0
  166. package/dist/providers/gmail/skill.generated.js +9 -0
  167. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  168. package/dist/providers/linear/index.js +352 -0
  169. package/dist/providers/linear/skill.generated.js +8 -0
  170. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  171. package/dist/providers/mixpanel/index.js +416 -0
  172. package/dist/providers/mixpanel/skill.generated.js +9 -0
  173. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  174. package/dist/providers/{notion.js → notion/index.js} +226 -239
  175. package/dist/providers/notion/skill.generated.js +10 -0
  176. package/dist/providers/overflow/index.d.ts +60 -0
  177. package/dist/providers/overflow/index.js +1535 -0
  178. package/dist/providers/overflow/skill.generated.js +8 -0
  179. package/dist/providers/planning-center/index.d.ts +59 -0
  180. package/dist/providers/planning-center/index.js +2199 -0
  181. package/dist/providers/planning-center/skill.generated.js +10 -0
  182. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  183. package/dist/providers/revenuecat/index.js +557 -0
  184. package/dist/providers/revenuecat/skill.generated.js +11 -0
  185. package/dist/providers/sheets/index.d.ts +54 -0
  186. package/dist/providers/sheets/index.js +1237 -0
  187. package/dist/providers/sheets/skill.generated.js +10 -0
  188. package/dist/providers/slides/index.d.ts +53 -0
  189. package/dist/providers/slides/index.js +2645 -0
  190. package/dist/providers/slides/skill.generated.js +9 -0
  191. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  192. package/dist/providers/stripe/index.js +266 -0
  193. package/dist/providers/stripe/skill.generated.js +11 -0
  194. package/dist/providers/tithely/index.d.ts +49 -0
  195. package/dist/providers/tithely/index.js +1003 -0
  196. package/dist/providers/tithely/skill.generated.js +8 -0
  197. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  198. package/dist/providers/vercel/index.js +1038 -0
  199. package/dist/providers/vercel/mcp-ownership.js +212 -0
  200. package/dist/providers/vercel/skill.generated.js +11 -0
  201. package/dist/registry.d.ts +108 -171
  202. package/dist/registry.js +513 -925
  203. package/dist/request-state.js +21 -0
  204. package/dist/resource-read.js +100 -0
  205. package/dist/result-shapes.js +17 -49
  206. package/dist/retired-toolkits.js +1 -1
  207. package/dist/routes/access-tokens.js +3 -6
  208. package/dist/routes/activity-context.js +21 -0
  209. package/dist/routes/activity-disclosure.js +40 -0
  210. package/dist/routes/activity.js +59 -21
  211. package/dist/routes/actor-labels.js +3 -50
  212. package/dist/routes/connect.js +221 -0
  213. package/dist/routes/credentials.js +15 -27
  214. package/dist/routes/mcp.js +356 -130
  215. package/dist/routes/oauth-client-metadata.js +15 -0
  216. package/dist/routes/oauth-management.js +45 -107
  217. package/dist/routes/oauth.js +136 -57
  218. package/dist/routes/operator.js +3 -3
  219. package/dist/routes/shared.d.ts +25 -64
  220. package/dist/routes/shared.js +160 -80
  221. package/dist/routes/skills.js +30 -0
  222. package/dist/routes/ui-config.js +225 -0
  223. package/dist/routes/ui-data.js +40 -35
  224. package/dist/routes/ui.js +66 -56
  225. package/dist/runtime/admission.js +4 -2
  226. package/dist/runtime/call-admission.js +3 -1
  227. package/dist/runtime/connector-scope.js +1 -4
  228. package/dist/runtime/run.js +10 -39
  229. package/dist/runtime/services.js +12 -117
  230. package/dist/runtime/shared-read.js +1 -3
  231. package/dist/runtime/storage.js +8 -1
  232. package/dist/sent-secrets.d.ts +52 -0
  233. package/dist/sent-secrets.js +474 -0
  234. package/dist/server.js +28 -71
  235. package/dist/skills.js +406 -174
  236. package/dist/sqlite.d.ts +48 -0
  237. package/dist/sqlite.js +166 -0
  238. package/dist/storage/keys.js +428 -0
  239. package/dist/storage/memory.js +7 -0
  240. package/dist/storage/sql.d.ts +66 -0
  241. package/dist/storage/sql.js +534 -0
  242. package/dist/timeout.js +4 -2
  243. package/dist/tool-name.js +17 -0
  244. package/dist/tool-safety.d.ts +9 -35
  245. package/dist/tool-safety.js +20 -40
  246. package/dist/types.d.ts +320 -54
  247. package/dist/types.js +0 -1
  248. package/dist/ui.d.ts +7 -19
  249. package/dist/ui.js +76 -90
  250. package/dist/url-safety.js +3 -6
  251. package/dist/usage-guide.js +137 -0
  252. package/dist/validate.d.ts +7 -0
  253. package/dist/validate.js +309 -47
  254. package/dist/version.d.ts +1 -7
  255. package/dist/version.js +3 -7
  256. package/dist/worker.d.ts +11 -0
  257. package/dist/worker.js +434 -0
  258. package/documentation/architecture.md +665 -230
  259. package/documentation/auth.md +962 -458
  260. package/documentation/code-mode.md +501 -312
  261. package/documentation/meta-tools.md +449 -200
  262. package/documentation/operator-tests.md +93 -0
  263. package/documentation/provider-migration-0.29.md +60 -0
  264. package/examples/worker/AGENTS.md +34 -12
  265. package/examples/worker/README.md +286 -255
  266. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  267. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  268. package/examples/worker/src/connecta.config.ts +151 -0
  269. package/examples/worker/src/index.ts +14 -197
  270. package/examples/worker/wrangler.jsonc +15 -35
  271. package/package.json +125 -35
  272. package/templates/node/.dockerignore +1 -2
  273. package/templates/node/.env.example +20 -21
  274. package/templates/node/AGENTS.md +21 -17
  275. package/templates/node/Dockerfile +3 -5
  276. package/templates/node/README.md +133 -58
  277. package/templates/node/docker-compose.yml +11 -12
  278. package/templates/node/package.json +4 -2
  279. package/templates/node/src/connecta.config.ts +119 -0
  280. package/templates/node/src/index.ts +9 -154
  281. package/templates/node/src/provision-token.ts +17 -0
  282. package/AGENTS.md +0 -170
  283. package/dist/artifacts/connector.d.ts +0 -58
  284. package/dist/artifacts/connector.js +0 -730
  285. package/dist/artifacts/document.js +0 -86
  286. package/dist/artifacts/guide.js +0 -140
  287. package/dist/artifacts/html-scan.js +0 -329
  288. package/dist/artifacts/json.js +0 -78
  289. package/dist/artifacts/kv-store.d.ts +0 -20
  290. package/dist/artifacts/kv-store.js +0 -140
  291. package/dist/artifacts/markdown.js +0 -478
  292. package/dist/artifacts/module.d.ts +0 -37
  293. package/dist/artifacts/module.js +0 -66
  294. package/dist/artifacts/operations.d.ts +0 -197
  295. package/dist/artifacts/operations.js +0 -1002
  296. package/dist/artifacts/refresh.d.ts +0 -55
  297. package/dist/artifacts/refresh.js +0 -236
  298. package/dist/artifacts/routes.js +0 -206
  299. package/dist/artifacts/types.d.ts +0 -214
  300. package/dist/artifacts/types.js +0 -22
  301. package/dist/artifacts/validate.d.ts +0 -62
  302. package/dist/artifacts/validate.js +0 -651
  303. package/dist/artifacts.d.ts +0 -6
  304. package/dist/artifacts.js +0 -9
  305. package/dist/auth/bearer.d.ts +0 -11
  306. package/dist/auth/bearer.js +0 -50
  307. package/dist/providers/linear.js +0 -217
  308. package/dist/providers/mixpanel.js +0 -247
  309. package/dist/providers/revenuecat.js +0 -317
  310. package/dist/providers/stripe.js +0 -253
  311. package/dist/providers/vercel.js +0 -1253
  312. package/dist/storage/file.d.ts +0 -15
  313. package/dist/storage/file.js +0 -455
  314. package/ethos.md +0 -93
  315. package/examples/worker/src/cloudflare-kv.ts +0 -43
  316. package/examples/worker/src/d1-activity-row.ts +0 -155
  317. package/examples/worker/src/d1-activity.ts +0 -147
  318. package/examples/worker/src/d1-storage.ts +0 -138
  319. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  320. package/templates/node/src/file-activity.ts +0 -182
@@ -1,23 +1,21 @@
1
1
  # Code mode — the guest API contract
2
2
 
3
- The normative description of what a program written for `execute_code` is
4
- promised: what it can reach, what it gets back, how failures look, what it may
5
- retry, what bounds it runs under, and what it leaves behind in activity. It is
6
- specified in prose first and implemented second.
7
-
8
- Two executors implement it: QuickJS in a child process on Node, and
9
- `DynamicWorkerExecutor` from `@cloudflare/codemode` on Workers. Divergence is a
10
- bug unless it appears in [Executor exceptions](#executor-exceptions), which
11
- names the reason; a third executor is implementable from this document alone.
12
- [`ethos.md`](../ethos.md) carries the verdicts behind the shape,
3
+ The `execute_code` contract: capabilities, results, failures, retries, limits,
4
+ and activity. Specified in prose first, implemented second.
5
+
6
+ Two executors implement it: QuickJS in a Node child process and `workerExecutor()`
7
+ from `@zackbart/connecta/worker` around upstream `DynamicWorkerExecutor` on Workers.
8
+ Divergence is a bug unless explained in [Executor exceptions](#executor-exceptions);
9
+ a third executor is implementable from this document alone.
10
+ [`PRINCIPLES.md`](https://github.com/zackbart/connecta/blob/main/PRINCIPLES.md) states the invariants,
13
11
  [`meta-tools.md`](./meta-tools.md) owns the top-level tool contract, and clause
14
12
  identifiers (`A1`, `E3`, …) are stable and cited by [Verification](#verification).
15
13
 
16
14
  ## Deploy-time capability
17
15
 
18
16
  The `executor` passed to `createConnecta()` is required. `tools/list` is exactly
19
- seven — `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`,
20
- `authorize_connector`, `get_result`, and `skills`. Construction fails when the
17
+ six — `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`,
18
+ `authorize_connector` and `skills`. Construction fails when the
21
19
  executor is missing, and the removed `surface` option is rejected rather than
22
20
  ignored ([#273](https://github.com/zackbart/connecta/issues/273)), as are the
23
21
  pause-only `execute.resumableWrites` and `execute.pausedRunTtlSeconds`
@@ -33,26 +31,37 @@ import { quickJsExecutor } from "@zackbart/connecta/quickjs";
33
31
  createConnecta({ executor: quickJsExecutor() /* connectors, auth, storage… */ });
34
32
  ```
35
33
 
36
- `quickJsExecutor()` runs each program in a disposable child-process sandbox; its
37
- CPU, wall-time, memory, stack, queue, result, log, and IPC bounds are configured
38
- on the executor. Server bundlers must keep the `@zackbart/connecta/quickjs`
39
- package files external so the child entry stays on disk. The
40
- [Node template](../templates/node/README.md) carries the complete setup.
34
+ `quickJsExecutor()` runs each program in a disposable child process, with CPU,
35
+ wall-time, memory, stack, queue, result, log, and IPC bounds on the executor.
36
+ Keep `@zackbart/connecta/quickjs` external in server bundles so the child entry
37
+ stays on disk. The [Node template](../templates/node/README.md) has the full setup.
41
38
 
42
- On Cloudflare Workers the Worker Loader binding is the sandbox, and Dynamic
43
- Workers require the Workers Paid plan:
39
+ On Workers the Worker Loader is the sandbox; Dynamic Workers require the Paid plan:
44
40
 
45
41
  ```ts
46
- createConnecta({ executor: new DynamicWorkerExecutor({ loader: env.LOADER }) });
42
+ import { workerExecutor } from "@zackbart/connecta/worker";
43
+
44
+ createConnecta({ executor: workerExecutor({ loader: env.LOADER }) });
47
45
  ```
48
46
 
49
- The supported constructor passes only `loader`; `bindings`, `modules`, or
50
- `globalOutbound` grant ambient guest authority and violate `P2`. The
51
- [Worker example](../examples/worker/README.md#code-mode) has the full setup.
47
+ The adapter uses only `loader` and an optional `timeout`. Its admission defaults
48
+ are two active runs, eight queued runs, and a five-second queue deadline, settable
49
+ through `admission`. Lease release disposes RPC and loader handles independently
50
+ of guest settlement. `bindings`, `modules`, or `globalOutbound` violate `P2`.
51
+ The [Worker example](../examples/worker/README.md#code-mode) has the full setup.
52
+
53
+ Replace `import { DynamicWorkerExecutor } from "@cloudflare/codemode";` with
54
+ `import { workerExecutor } from "@zackbart/connecta/worker";` and use the wiring above.
55
+ Keep the peer. Unbranded executors throw, including bundled upstream copies.
56
+ Shipped executors carry a non-enumerable `Symbol.for("connecta.executor")` version/lifecycle brand.
57
+ Custom sandboxes import `customExecutor` from `@zackbart/connecta`, then configure
58
+ `executor: customExecutor(myExecutor, { lifecycle: "self-managed" })`. They own guest termination
59
+ and cleanup at budget exhaustion, cancellation, and deadlines; the wrapper only delegates methods.
52
60
 
53
61
  ## What an executor must implement
54
62
 
55
- The host side of the seam is two types in `src/types.ts` and nothing else.
63
+ The Promise provider/result seam lives in `src/types.ts`. Custom implementations
64
+ also need the lifecycle wrapper above; structural compatibility alone is insufficient.
56
65
 
57
66
  ```ts
58
67
  interface Executor {
@@ -70,6 +79,8 @@ interface ExecutorProvider {
70
79
  interface ExecuteResult {
71
80
  result: unknown; // the program's resolved value
72
81
  error?: string; // set instead of result when the run failed
82
+ failure?: { name: string; line?: number; call?: CallErrorDetails;
83
+ timeout?: { elapsedMs: number; deadlineMs: number } };
73
84
  logs?: string[]; // captured console output, in call order
74
85
  }
75
86
  ```
@@ -78,34 +89,38 @@ Connecta passes exactly one provider, named `connecta`. An executor must:
78
89
 
79
90
  1. **Expose each provider as a guest global** whose properties are its `fns`,
80
91
  called with the program's arguments and awaited. Connecta supplies `search`,
81
- `describe`, `call`, and `emit`.
92
+ `describe`, `call`, `read`, `result`, `skill`, and `emit`.
82
93
  2. **Evaluate `prelude` after the provider globals exist and before the
83
- program.** It is trusted host code; connecta uses it to restore typed host
84
- errors without exposing the private error frame.
94
+ program.** It is trusted host code that installs the immutable namespace
95
+ and waits for emission acknowledgements. Capture runner intrinsics first;
96
+ privileged initialization must be single-use.
85
97
  3. **Marshal values as JSON** both directions (`P3`), and reject a host call
86
98
  whose function is not an own property of `fns` — the guest can ask for
87
99
  inherited members too.
88
- 4. **Return, never throw, for a failed program**: set `error` to the guest's
89
- message, leave `result` undefined. `createExecuteTool` reads `error` first and
90
- matches it back to the failures recorded during the run, which is how an
91
- uncaught tool failure keeps its type (`E1`).
100
+ 4. **Return, never throw, for a failed program.** Set `error` and leave
101
+ `result` undefined. Rebuild a rejected host `InvocationFailure` as a guest
102
+ Error with `code`, `retryable`, and `details`, and retain its identity in
103
+ executor-owned state. If that same Error escapes the program, return its
104
+ typed details in `failure.call`. A wrapped or copied Error is `program_error`.
105
+ Construct outcomes through the host-owned RPC return or QuickJS host bridge,
106
+ never by parsing guest stdout, prints, returned values, or thrown look-alikes.
107
+ No secret held in the guest realm authenticates anything. Guest module
108
+ specifiers must not resolve runner internals, including dynamic imports.
109
+ For compilation failures, return `failure: { name, line? }`. For a sandbox
110
+ timeout, add `failure.timeout: { elapsedMs, deadlineMs }`.
92
111
  5. **Capture `console.log`, `console.warn`, and `console.error`** into `logs` in
93
112
  call order (`R5`), bounding what it retains.
94
- 6. **Bound the guest**: wall clock, memory, stack, and CPU (`L3`, `L5`), staying
95
- inside the tested `P2`/`X5` boundary. Settle `execute()` on your own
96
- deadline: connecta stops waiting at `execute.watchdogMs` and reports the
97
- sandbox unresponsive (`L3`).
98
- 7. **Grant no ambient authority of its own.** Never back this with `eval` or
99
- `node:vm`: the sandbox is a containment layer on top of connecta's boundary,
100
- not a replacement for it, and every capability arrives through `fns`.
101
-
102
- Optionally implement `AdmittingExecutor` (`acquire()` returning a lease whose
103
- `execute` runs once) for bounded admission (`L7`) and `close()` for shutdown;
104
- connecta wraps a plain `Executor` with `withExecutorAdmission` otherwise. The
105
- optional `name` — else a class's constructor name, which a minifier may rewrite
106
- — is the executor name `/health` and `connecta doctor` report.
107
- [Emitted output](#emitted-output) asks nothing of an executor: `connecta.emit`
108
- is just another provider function (`M8`).
113
+ 6. **Bound the guest**: wall clock, memory, stack, and CPU (`L3`, `L5`), within
114
+ `P2`/`X5`. Settle `execute()` on your own deadline; connecta stops waiting
115
+ at `execute.watchdogMs` and reports the sandbox unresponsive (`L3`).
116
+ 7. **Grant no ambient authority.** Never use `eval` or `node:vm`: sandboxing
117
+ supplements connecta's boundary; every capability arrives through `fns`.
118
+
119
+ Optional `AdmittingExecutor.acquire()` returns a once-executed lease for bounded
120
+ admission (`L7`); otherwise connecta uses `withExecutorAdmission`. `close()` handles
121
+ shutdown. `/health` and `connecta doctor` report `name`, falling back to the
122
+ constructor name, which minifiers may rewrite. [Emission](#emitted-output) is
123
+ another provider function (`M8`).
109
124
 
110
125
  ## The program
111
126
 
@@ -120,7 +135,7 @@ statements, and bare statement bodies are outside host recovery. QuickJS may
120
135
  wrap a bare body when driven directly; that is outside the portable contract.
121
136
 
122
137
  **P2.** The only capabilities in the contract are `connecta.search`,
123
- `connecta.describe`, `connecta.call`, `connecta.emit`, and `console.log` /
138
+ `connecta.describe`, `connecta.call`, `connecta.read`, `connecta.result`, `connecta.skill`, `connecta.emit`, and `console.log` /
124
139
  `console.warn` / `console.error`, captured and returned. Anything else a runtime
125
140
  exposes is outside the portable contract and must not be used. QuickJS grants
126
141
  none of it. A loader-only Dynamic Worker denies external egress and filesystem
@@ -163,10 +178,10 @@ belonged to shortcut dispatch and stay retired; nothing reuses those ids.
163
178
 
164
179
  ## The surface
165
180
 
166
- Four functions, all `async`: `search`, `describe`, `call`, and `emit`. Nothing
167
- else works. Reading any other property yields a function — the guest namespace
168
- is a Proxy, so `typeof connecta.toString` is `"function"` — but *calling* it
169
- fails, because the host resolves only own members of the provider's `fns`.
181
+ Seven functions: `search`, `describe`, `call`, `read`, `result`, `skill`, and `emit`.
182
+ All return awaitable values; emission also works without `await`. The namespace
183
+ has no inherited members. Unknown properties are undefined, and calling them
184
+ fails as a guest `TypeError`. The host also dispatches only own members of `fns`.
170
185
 
171
186
  ### connecta.search
172
187
 
@@ -178,15 +193,17 @@ const page = await connecta.search({
178
193
  limit: 8, // 1–100, default 8
179
194
  offset: 0,
180
195
  fullDescriptions: false,
181
- includeSchemas: "compact", // or "json" / "typescript"
196
+ includeSchemas: "json", // default; or "compact" / "typescript"
182
197
  includeSchemaKeys: true, // default true in code mode
183
198
  });
184
199
  ```
185
200
 
186
- **S1.** Returns one flat page: `{ tools, total, offset, limit, hasMore }`, plus
187
- `nextOffset` when more remains and `matchMode: "partial"` when no tool matched
188
- every term. Top-level `search_tools` is different — it returns
189
- `{ connectors: [{ id, tools }], total, offset, limit, hasMore }`. Ranking is
201
+ **S1.** Returns the same flat page as top-level `search_tools`:
202
+ `{ catalogErrors, tools, total, offset, limit, hasMore }`, plus `nextOffset`
203
+ when more remains and `matchMode: "partial"` when no tool matched every term.
204
+ Read `catalogErrors` and any `absence` before selecting a tool. Catalog failures
205
+ carry recovery actions at the top of the page; an absent service returns no
206
+ lookalikes. Both paths use only the caller's endpoint/pool and grants. Ranking is
190
207
  [lexical discovery](./meta-tools.md#lexical-discovery)'s, and rows expose
191
208
  neither lexical scores nor per-result coverage. An empty or whitespace-only
192
209
  query browses; non-empty input with no ASCII lexical terms returns no tools plus
@@ -201,16 +218,20 @@ bounded `guideSummary`, requested or not. An output shape learned under `S9`
201
218
  also carries `outputSchemaSource: "observed"`; provider declarations carry no
202
219
  source marker.
203
220
 
221
+ Programs default to `includeSchemas: "json"`: `inputSchema` and available
222
+ `outputSchema` are JSON Schema values, labeled `schemaFormat: "json"`.
223
+ Explicit `includeSchemas: "compact"` returns strings labeled
224
+ `schemaFormat: "text"`; TypeScript signatures carry the same text label.
204
225
  Compact shapes omit property prose and put required fields first, under these
205
226
  bounds:
206
227
 
207
- | Bound | Value |
208
- | --- | --- |
209
- | `execute_code` source | ≤ 65,536 UTF-8 bytes, checked before executor admission; over-limit source returns `invalid_args` |
210
- | Rendered shape | 1,024 UTF-8 bytes each |
211
- | Work per shape | 2,000 visits across schema nodes, property and required names, literal values, any constraint-free retry, and its key-only fallback |
212
- | Each enum node | 256 of those bytes |
213
- | Resolved `$ref` text | reused within one walk |
228
+ | Bound | Value |
229
+ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
230
+ | `execute_code` source | ≤ 65,536 UTF-8 bytes, checked before executor admission; over-limit source returns `invalid_args` |
231
+ | Rendered shape | 1,024 UTF-8 bytes each |
232
+ | Work per shape | 2,000 visits across schema nodes, property and required names, literal values, any constraint-free retry, and its key-only fallback |
233
+ | Each enum node | 256 of those bytes |
234
+ | Resolved `$ref` text | reused within one walk |
214
235
 
215
236
  About three near-cap enum nodes therefore coexist while the final quarter stays
216
237
  for surrounding syntax, and above 1,024 bytes the global fallback applies.
@@ -229,7 +250,7 @@ use `format: "json"` or JSON search.
229
250
  `includeSchemas: "typescript"` spends those same bounds on a different
230
251
  rendering: one `signature` string in place of `inputSchema` and
231
252
  `outputSchema`, reading `(args: { runId: number }) => Promise<Run>` for the
232
- value `connecta.call` returns. It is documentation for the program's author —
253
+ `data` value `connecta.call` returns. It is documentation for the program's author —
233
254
  the program itself is still JavaScript, and a type annotation copied into it
234
255
  is a syntax error (`P5`). Truncation keeps compact's flags and
235
256
  turns what compact would print as a name or raw JSON into a marked `unknown`;
@@ -239,14 +260,10 @@ its degradation rules are specified once, under
239
260
  signature is byte-identical to the one `search_tools` returns.
240
261
 
241
262
  **S1a.** `connector` loads only the named catalog; omit it only when the
242
- integration is ambiguous, because an unscoped search fans out across every
243
- configured connector. `safety: "readOnly"` returns exactly the tools
244
- `connecta.call` runs unasked; `"approvalRequired"` returns the complementary
245
- fail-closed class, false, missing, and contradictory annotations included,
246
- whose calls a program has refused (`E4`) unless config exempts them (`W12`),
247
- which a row marks with `approval: "exempt"` without moving it out of that
248
- class; omitted or `"all"` preserves the complete catalog. These filters grant
249
- no authority and change no admission decision.
263
+ integration is ambiguous. `safety: "readOnly"` selects stored read verdicts;
264
+ `"approvalRequired"` selects stored write verdicts; omitted or `"all"` selects
265
+ both. Search and describe rows carry `classification`. These filters grant no
266
+ authority and change no pool trust or call admission decision.
250
267
 
251
268
  **S2.** A requested object schema carries `inputKeys`, `requiredInputKeys`
252
269
  (declared properties only), and `outputKeys`: the names the rendered schema
@@ -278,12 +295,14 @@ hint and the stable `code`, `retryable`, and `details` fields (`E1`).
278
295
  const one = await connecta.describe({ address: "ci.get_run" });
279
296
  const many = await connecta.describe({
280
297
  addresses: ["ci.get_run", "ci.get_job_logs"], // ≤ 100
281
- format: "compact", // or "json" / "typescript"
298
+ format: "json", // default; or "compact" / "typescript"
282
299
  fullDescriptions: false,
283
300
  });
284
301
  ```
285
302
 
286
- **S4.** Returns `{ tools }` in order, one entry per address; one bad address
303
+ **S4.** Defaults to JSON Schema values with `schemaFormat: "json"`.
304
+ Explicit compact schemas and TypeScript signatures have `schemaFormat: "text"`.
305
+ Returns `{ tools }` in order, one entry per address; one bad address
287
306
  never fails the whole call. An unknown address or failed catalog returns `error`
288
307
  plus typed `errorDetails` (`code`, `message`, `retryable`). Misses carry a
289
308
  route-aware `nextAction`, a close miss may add three canonical `suggestions`,
@@ -302,15 +321,22 @@ says so inside its `Promise<…>`.
302
321
 
303
322
  ### connecta.call
304
323
 
324
+ The published `GuestApi` type describes the supplied global for editors.
325
+ The executable usage guide checks its API notation against those declarations;
326
+ programs still use plain JavaScript.
327
+
305
328
  ```js
306
- const run = await connecta.call("ci.get_run", { runId: 42 });
329
+ const { data: run, format } = await connecta.call("ci.get_run", { runId: 42 });
330
+ // Equivalent: connecta.call({ address: "ci.get_run", args: { runId: 42 }, timeoutMs: 30_000 })
307
331
  ```
308
332
 
309
- **S5.** Takes a canonical address and one arguments object; returns the tool's
310
- value already unwrapped. For an MCP connector that means `structuredContent`
311
- when present, otherwise text content JSON-parsed when it parses and the raw text
312
- when it does not; a downstream result flagged `isError` throws. Omitted `args`
313
- is treated as `{}`.
333
+ **S5.** Takes a canonical address, optional arguments, and optional
334
+ `{ timeoutMs }`; the object form is `{ address, args?, timeoutMs? }`. A malformed
335
+ signature fails with `invalid_args` and both exact signatures, before discovery.
336
+ Returns `{ data, format: "json" | "text" }`. MCP `toolResult` or
337
+ `structuredContent` is JSON; all-text content is JSON when it parses, otherwise
338
+ text. API string values are text, other API values are JSON. Check `format`
339
+ before treating `data` as an object. A downstream `isError` throws.
314
340
 
315
341
  **S6.** Every call goes through the same catalog, fail-closed read-only
316
342
  predicate, admission, credential containment, timeout classification, health
@@ -320,6 +346,29 @@ program does decides it. The sandbox is an additional containment layer, not a
320
346
  second implementation of the boundary, and nothing a program does widens what
321
347
  it can reach.
322
348
 
349
+ ### connecta.result and connecta.skill
350
+
351
+ `await connecta.result(id, { offset?, maxBytes? })` reads a stashed direct-call
352
+ result bound to the admitted subject, principal, endpoint/pool, request origin,
353
+ connector, and tool. It returns
354
+ `{ resultId, offset, bytes, totalBytes, hasMore, nextOffset?, format: "text", text }`.
355
+ Offsets and sizes are UTF-8 bytes; the page cap is the original call's inline cap.
356
+ Follow `nextOffset`, reassemble inside the program, and return a reduced value.
357
+ Before returning a page, the host rechecks current authentication, connector/tool
358
+ grants, pool membership, and endpoint trust against both the original and current
359
+ tool classification. A read-only endpoint cannot page a write result, so a
360
+ direct write there returns an inline truncation notice without stashing the
361
+ result or advertising a paging action. Binding
362
+ mismatches, revoked access, and old entries without bindings are `not_found`, as
363
+ are unknown or expired ids; a storage failure is `unavailable`. IDs are random UUIDs.
364
+ The `get_result` meta-tool is removed.
365
+
366
+ `await connecta.skill(name)` resolves the same URI or compatibility name as
367
+ top-level `skills`, within the admitted connector view. It reads the registry
368
+ behind native `skills/get` and `resources/read`, including opted-in downstream
369
+ skills. It returns
370
+ `{ name, format: "text", text }`; an unknown name is `not_found`.
371
+
323
372
  ### Parallel calls
324
373
 
325
374
  **S7.** Use `Promise.all` for independent calls when any failure should fail the
@@ -331,7 +380,7 @@ as sequential calls. There is no separate batch size or result contract.
331
380
  `details`. Project those fields before returning — an `Error` object is not a
332
381
  JSON result contract.
333
382
 
334
- **S9.** A successful explicitly read-only call whose provider declared no
383
+ **S9.** A successful call classified as a read whose provider declared no
335
384
  `outputSchema` passively learns one from the unwrapped result. The observation
336
385
  keeps field names and broad JSON types only — no arguments, scalar values, raw
337
386
  results, code, credentials, or errors — and property names may be user-authored.
@@ -350,6 +399,69 @@ successful call. No discovery read, timer, refresh, background job, or storage
350
399
  adapter executes or persists work for this cache: the result-sampling refusal in
351
400
  [#282](https://github.com/zackbart/connecta/issues/282) stands.
352
401
 
402
+ ### connecta.read
403
+
404
+ ```js
405
+ const resource = await connecta.read(
406
+ "resource://docs/" + encodeURIComponent("docs://manual/start"),
407
+ );
408
+ return resource.contents.map(content => content.text);
409
+ ```
410
+
411
+ **S10.** Reads a downstream resource through a configured connector that
412
+ implements `readResource`. The routing URI is exactly
413
+ `resource://<connectorId>/<encodeURIComponent(downstreamUri)>`, at most 8,192
414
+ UTF-8 bytes. The downstream URI must have a scheme and no control characters.
415
+ Encode it once, including any path, query and fragment. Bare URLs, malformed
416
+ encoding, routing userinfo, ports and extra routing query or fragment fields
417
+ fail with `invalid_args`. Remote MCP connectors first load the complete,
418
+ paginated resources and URI-template inventories through the same host-auth
419
+ partitioned catalog cache. The decoded URI must exactly match an advertised
420
+ resource or match an advertised URI template. An unmatched URI fails with a
421
+ fixed `not_found` message before any downstream read. Template matching keeps
422
+ the scheme and authority fixed and supports bounded RFC 6570 scalar and list
423
+ expansions, including prefix modifiers and safe multi-segment `{+path}` and
424
+ `{/p*}` paths. Variables cannot supply authority, backslashes, dot segments,
425
+ a scheme change, query delimiters, controls or Unicode format characters,
426
+ including through repeated percent encoding. Initial percent encoding and
427
+ UTF-8 decoding stay strict. Later decoding layers preserve literal percent
428
+ signs produced by `%25` while checking every remaining `%HH` escape. The
429
+ matcher scans forward to the
430
+ leftmost following literal without backtracking. Adjacent expressions and
431
+ literals that the preceding expression can contain are refused with
432
+ `resource_template_ambiguous`. Matching all templates is capped at 262144
433
+ combined template and URI characters per read, with
434
+ `resource_match_budget_exceeded` beyond that bound. Operator status retains
435
+ each typed refusal once, without URI or template text. Mixed exploded
436
+ composites remain unsupported.
437
+ Connecta passes an admitted decoded URI as an opaque MCP `resources/read`
438
+ argument to the configured endpoint. It never fetches that
439
+ URI or creates a connector from it.
440
+
441
+ Resources are protocol reads in both `read-only` and `trusted` pools. A read
442
+ requires a grant to the whole connector through the endpoint/pool and identity
443
+ intersection. Exact-tool grants, including guarded grants, authorize no
444
+ resources. An absent, hidden, tool-restricted or resource-incapable connector
445
+ fails with `unknown_address`. The URI and extra arguments cannot select a
446
+ different credential owner or downstream subject. Personal credentials come
447
+ from the admitted principal, just as they do for tool calls.
448
+
449
+ The return is the downstream JSON `{ contents }` result, with text or base64
450
+ blob entries and available metadata. It is not unwrapped or JSON-parsed.
451
+ Every read spends one shared host call and uses the connector's call admission
452
+ with `toolName: "resources/read"` and `args: { uri: downstreamUri }`. One deadline
453
+ covers admission and dispatch; cancellation ends the wait and releases its
454
+ permit. Reads share the execution's downstream request scope. They make one
455
+ attempt, bypass response caching, and never start authorization. A downstream
456
+ resource miss is `not_found`; a mid-call input request is
457
+ `input_required_unsupported`. Other failures use the existing typed error
458
+ contract, including credential recovery. Values and failures cross the same
459
+ credential redaction boundary as `call`. Diagnostics use the fixed operation
460
+ name `read`; resource URIs and bodies enter no activity or diagnostic record.
461
+ This function exposes no resource listing, template discovery, subscriptions
462
+ or upward proxy to programs. Native upward skill resources are separate
463
+ [Phase 3](https://github.com/zackbart/connecta/issues/707) work.
464
+
353
465
  ### connecta.emit
354
466
 
355
467
  ```js
@@ -362,24 +474,24 @@ clauses are [Emitted output](#emitted-output) (`M1`–`M10`).
362
474
  ## Errors
363
475
 
364
476
  **E1.** There are three error channels. Connecta failures are typed whether
365
- caught or uncaught.
477
+ caught or uncaught. Host-call budget exhaustion ends the host run and blocks host
478
+ access (`L4`); Workers guest computation may continue briefly until teardown (`X3`).
366
479
 
367
- | Channel | Shape | Typed? |
368
- | --- | --- | --- |
369
- | A caught Connecta host failure | `Error` with `message`, `code`, `retryable`, and `details` | yes |
370
- | An uncaught **tool or discovery** failure, as the model sees it | `{ error: { code, message, retryable, … } }` with `isError` | yes |
371
- | Program or execution failure (`E5`, `E6`, a bridge bound in `L6`) | error text | no |
480
+ | Channel | Shape | Typed? |
481
+ | ------------------------------------------------------------------------- | ----------------------------------------------------------- | ------ |
482
+ | A caught Connecta host failure | `Error` with `message`, `code`, `retryable`, and `details` | yes |
483
+ | An uncaught **tool, resource or discovery** failure, as the model sees it | `{ error: { code, message, retryable, … } }` with `isError` | yes |
484
+ | Program or execution failure (`E5`, `E6`, a bridge bound in `L6`) | `{ error: { code, message, retryable, details? } }` | yes |
372
485
 
373
- Both executor bridges reduce a rejected host call to `new Error(message)`, and
374
- connecta restores the typed failure in a trusted prelude with a per-execution
375
- authenticated frame (`X11`). `message` stays human text, capped at 2,000
486
+ Both executor bridges rebuild typed host rejections as guest Errors and retain
487
+ their identity in executor-owned state (`X11`). `message` stays human text, capped at 2,000
376
488
  JSON-serialized characters including quotes and an `…` marker when clipped.
377
489
  `code` and `retryable` are the stable branch fields; `details` carries the host
378
490
  classification and fits 3,700 serialized characters. Optional recovery metadata
379
491
  that would exceed that bound is omitted whole, preserving `code`, `message`,
380
492
  `retryable`, and `retryAfterMs`, because a clipped recovery address or argument
381
- describes a different call. This covers `call`, `search`, `describe`, `emit`, and
382
- the host-call budget. Program-authored errors stay untyped, and code must never
493
+ describes a different call. This covers `call`, `read`, `search`, `describe`, `result`, `skill`, `emit`, and
494
+ write and emitted-output budgets. Program-authored errors use `program_error`; code must never
383
495
  parse error prose. An `unavailable` classification may add `details.host`, an
384
496
  HTTP(S) origin of at most 253 UTF-8 bytes, and `details.code`, a validated
385
497
  network errno, undici transport code, or `timeout` of at most 32 bytes; neither
@@ -389,26 +501,27 @@ enters activity.
389
501
  may do. A provider maps each downstream failure to the code that tells the
390
502
  caller what to do next, and never invents a cause it was not told.
391
503
 
392
- | Code | Raised when | `retryable` |
393
- | --- | --- | --- |
394
- | `unknown_address` | no connector owns the address | false |
395
- | `unknown_tool` | the connector has no such tool | false |
396
- | `destructive_tool_requires_approval` | the tool is neither explicitly read-only nor config-exempt (`E4`) | false |
397
- | `auth_required` | the credential is missing, expired, or rejected | false |
398
- | `invalid_args` | arguments or discovery bounds were rejected | false |
399
- | `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 | false |
400
- | `conflict` | the write named a base version someone else already moved past; nothing changed. `details.current` says where things stand (at most 20 whole-number entries) — re-read, reapply, retry with that base | false |
401
- | `input_required_unsupported` | a downstream asked for mid-call input | false |
402
- | `rate_limited` | the downstream reported a rate limit | true |
403
- | `unavailable` | the downstream is down or unreachable; optional sanitized `details.host` and `details.code` describe the transport failure without paths, queries, credentials, or provider prose | true |
404
- | `timeout` | the per-call deadline (`execute.hostCallTimeoutMs`, default 15 s) expired | true |
405
- | `cancelled` | the run ended while this call was in flight (`E5`) | false |
406
- | `connector_call_failed` | anything else the connector threw | per message |
407
- | `catalog_lookup_failed` | the connector's catalog could not be loaded | per cause |
408
- | `result_processing_failed` | the result could not be prepared | per message |
409
- | `result_too_large` | a discovery response exceeded its byte bound | false |
410
- | `budget_exceeded` | the run exhausted a host-call, write, or emitted-output budget | false |
411
- | `write_outcome_unknown` | an exempt write was sent and no answer came back; it is never sent again (`W9`) | false |
504
+ | Code | Raised when | `retryable` |
505
+ | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
506
+ | `unknown_address` | no visible connector owns the tool address or grants the resource reader | false |
507
+ | `unknown_tool` | the connector has no such tool | false |
508
+ | `destructive_tool_requires_approval` | a program in a read-only pool attempted a write (`E4`) | false |
509
+ | `auth_required` | the credential is missing, expired, or rejected | false |
510
+ | `invalid_args` | arguments or discovery bounds were rejected | false |
511
+ | `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 | false |
512
+ | `conflict` | the write named a base version someone else already moved past; nothing changed. `details.current` says where things stand (at most 20 whole-number entries) — re-read, reapply, retry with that base | false |
513
+ | `input_required_unsupported` | a downstream asked for input; use `nextAction` for the equivalent direct call | false |
514
+ | `rate_limited` | the downstream reported a rate limit | true |
515
+ | `unavailable` | the downstream is down or unreachable; optional sanitized `details.host` and `details.code` describe the transport failure without paths, queries, credentials, or provider prose | true |
516
+ | `timeout` | a call or sandbox deadline expired; details name operation, stage, elapsedMs, and deadlineMs | per operation |
517
+ | `program_error` | guest JavaScript failed; `details.name`, `line`, and a fixed repair hint describe it | false |
518
+ | `cancelled` | the run ended while this call was in flight (`E5`) | false |
519
+ | `connector_call_failed` | anything else the connector threw | per message |
520
+ | `catalog_lookup_failed` | the connector's catalog could not be loaded | per cause |
521
+ | `result_processing_failed` | the result could not be prepared | per message |
522
+ | `result_too_large` | a discovery response exceeded its byte bound | false |
523
+ | `budget_exceeded` | the run exhausted a host-call, write, or emitted-output budget | false |
524
+ | `write_outcome_unknown` | a trusted-pool write was sent and no answer came back; it is never sent again (`W9`) | false |
412
525
 
413
526
  **E3.** `auth_required` carries the same recovery envelope as `call_tool`:
414
527
  `connector`, `operation`, `recovery` (`oauth`, `operator_config`, or
@@ -416,8 +529,8 @@ caller what to do next, and never invents a cause it was not told.
416
529
  sentence. A program cannot recover credentials — only an operator can — so stop
417
530
  and let the failure reach the model.
418
531
 
419
- **E4.** An unannotated, write-capable, or destructive tool never runs in a
420
- program unless config exempts it (`W12`). It is refused with
532
+ **E4.** A tool classified as a write never runs in a
533
+ program unless the pool is trusted (`W12`). It is refused with
421
534
  `destructive_tool_requires_approval` before validation and before anything is
422
535
  sent, `nextAction` carrying its canonical address to `call_destructive_tool`
423
536
  plus the original arguments when they fit the 512-byte echo budget — whole or
@@ -426,7 +539,7 @@ call is the approval, and the only one: generated code can neither mint the
426
539
  capability nor approve its own write, and the model's short `reason` grants no
427
540
  authority and never goes downstream.
428
541
 
429
- **E5.** Failures of the *execution*, not of a call, never appear inside the
542
+ **E5.** Failures of the _execution_, not of a call, never appear inside the
430
543
  guest: admission rejection (`executor_overloaded`, retryable, with
431
544
  `retryAfterMs`), cancellation (`executor_cancelled`), shutdown
432
545
  (`executor_closed`), deadline expiry, an executor that never settles (`L3`),
@@ -436,19 +549,33 @@ is cancelled fails with `cancelled`, catchable on the way out but never worth
436
549
  acting on (`Y3`). When shutdown tears down a program that had already started,
437
550
  accepted blocks are reported as discarded under `M4`; a failure before execution
438
551
  started carries no discard fields. A returned `error` field is a failure even
439
- when empty — an empty string reports `executor_failed` with
440
- `Error: Execution failed without an error message.`
441
-
442
- **E6.** An error the program raises itself — a `TypeError`, a call to a
443
- `connecta` member that is not a provider function (an inherited one like
444
- `toString` included), a `throw` of its own — ends the run with an untyped error
445
- result carrying that message. One precedence rule: connecta recognizes an
446
- escaped tool failure by its message, exactly first and by containment second for
447
- messages of at least eight characters, so a program that *wraps* a failure's
448
- message still reports the underlying typed failure — keeping the type beats
449
- keeping the prose. Matching retains only the most recent 64 failures per
450
- execution, caught refusals included; an older escaped message stays an untyped
451
- execution failure. An empty terminal error uses the fixed message in `E5`.
552
+ when empty. An empty string reports `program_error` with
553
+ `Program Error: Execution failed without an error message.`
554
+
555
+ **E6.** A guest `TypeError`, `ReferenceError`, syntax error, invented API, or
556
+ program-authored throw is `program_error`. `details.name` is a bounded engine
557
+ error name; `details.line` is the source line when the runtime supplies one,
558
+ otherwise null. Fixed hints repair imports/require/filesystem access, shadowed
559
+ `connecta`, and invented globals such as `callTool` and `mixpanel`.
560
+
561
+ An unchanged uncaught host error retains its classification through Error
562
+ identity. A new error containing its message is a new program error. Copied
563
+ public `code`/`details`, printed text, and returned outcome-shaped objects carry
564
+ no authority. Host-call counts stay in host state. The guest JSON codec, Object
565
+ prototype, and Promise prototype are immutable. Promise race and species are
566
+ protected too. Host completion uses captured intrinsics so guest edits cannot
567
+ change bridge decoding or async return adoption. Worker guest modules contain
568
+ only the user callback and an import helper limited to runtime builtins.
569
+ Relative and other guest module imports cannot reach runner code. Trusted
570
+ wrappers and preludes remain outside the guest's lexical scope, and privileged
571
+ initialization is single-use. A separate private runner module captures and
572
+ freezes its references before the guest module evaluates, including the guest
573
+ namespace initializer. The entrypoint imports that runner before its guest
574
+ dependency. Before loading, the adapter parses the assembled guest export and
575
+ requires exactly one expression with no additional statements. Normalized
576
+ multi-statement bodies remain one async arrow expression.
577
+ Program diagnostic fields are bounded before transport, including escaped text;
578
+ a large custom error name cannot turn a failure into a truncated success.
452
579
 
453
580
  **E7.** `retryable` for `unknown_address`, `unknown_tool`, and
454
581
  `destructive_tool_requires_approval` is pinned false, never inferred from an
@@ -495,15 +622,16 @@ The envelope is itself bounded as serialized, so `totalChars` is always the true
495
622
  size of what the program returned and truncation happens exactly once no matter
496
623
  how many hops the value takes.
497
624
 
498
- **R3.** Truncation is a *successful* result: the program ran but returned too
625
+ **R3.** Truncation is a _successful_ result: the program ran but returned too
499
626
  much. Run a program that returns less; that is why the envelope says so.
500
627
 
501
628
  **R4 (verdict: no result paging for programs).** Program results have no
502
- `get_result` handle. A program can shrink its return; paging would reward
629
+ `connecta.result` handle. A program can shrink its return; paging would reward
503
630
  unprojected data.
504
631
 
505
632
  For non-repeatable writes, inspect and reduce the full result before return,
506
- or page a direct `call_destructive_tool` result with `get_result`. Sampling a
633
+ or page a direct `call_destructive_tool` result on a trusted endpoint with
634
+ `connecta.result`. Sampling a
507
635
  program result may discard the only copy.
508
636
 
509
637
  **R5.** `console.log`, `console.warn`, and `console.error` are captured in call
@@ -512,18 +640,22 @@ truncation marker. Logs survive program failure through either a returned error
512
640
  result or a thrown error carrying `logs: string[]`. QuickJS streams captured
513
641
  entries to its parent and preserves the received prefix on cancellation,
514
642
  shutdown, deadline termination, child crashes, and IPC failures (`X4`). How a
515
- non-string argument renders is not contract (`X4`).
516
-
517
- **R6.** Nothing else is added to a normal program result. `diagnostics: true`
518
- adds one request-local, payload-free `diagnostics` block; a program that emitted
519
- adds `emitted: N` and its blocks (`M2`). Omitted, `false`, and emit-free are
520
- byte-for-byte the ordinary response path
521
- ([#247](https://github.com/zackbart/connecta/issues/247)).
643
+ non-string argument renders is not contract (`X4`). At terminal host-call
644
+ budget exhaustion, QuickJS returns its streamed prefix; a Dynamic Worker
645
+ has no host log stream and cannot supply logs from its suspended guest (`L4`).
646
+
647
+ **R6.** Every run returns `hostCalls: { attempted, admitted, succeeded, failed }`,
648
+ including admission failure, cancellation, budget refusal, and code-size refusal.
649
+ Calls include search, describe, call, result, and skill. `emit` has separate budgets.
650
+ An abandoned host call counts as failed when the run ends; guest catches do not
651
+ change the counts. These numbers are outside the program's returned `result`.
652
+ `diagnostics: true` adds the existing payload-free timing block. Emitting adds
653
+ `emitted: N` and its content blocks.
522
654
 
523
655
  **R7.** Diagnostic timing separates admission, provider setup, total executor
524
656
  wall time, catalog work, and connector work. Catalog and connector values are
525
657
  cumulative, so parallel work can exceed executor wall time. Each used operation
526
- kind (`search`, `describe`, `call`) gets one aggregate with count, failures,
658
+ kind (`search`, `describe`, `call`, `read`) gets one aggregate with count, failures,
527
659
  duration, returned serialized bytes, and catalog/connector time.
528
660
 
529
661
  **R8.** Diagnostics contain measurements and fixed operation names only: no
@@ -533,9 +665,13 @@ request; it is not activity, a session, or a stream.
533
665
 
534
666
  ## Emitted output
535
667
 
668
+ Images accept only `image/png`, `image/jpeg`, `image/gif`, or `image/webp`
669
+ and nonempty canonical base64. Whitespace, URL-safe alphabets, bad padding,
670
+ and nonzero pad bits are rejected before collection. Size budgets still apply.
671
+
536
672
  MCP-native output a return value cannot carry: base64 is not projectable, so a
537
673
  block that survives intake uncapped (`S5`) must not die at the `R2` exit guard.
538
- The refused alternatives are in `ethos.md`
674
+ The earlier alternatives are in [decision history](https://github.com/zackbart/connecta/blob/main/decisions/0001-ethos-verdict-table.md)
539
675
  ([#267](https://github.com/zackbart/connecta/issues/267),
540
676
  [#270](https://github.com/zackbart/connecta/issues/270)).
541
677
 
@@ -552,18 +688,25 @@ a successful result, appended to `content` after the JSON envelope, which gains
552
688
  response (`R6`). `structuredContent` stays the envelope alone — emission is
553
689
  presentation, not a second data channel.
554
690
 
555
- **M3.** Return value and emission are independent: `R2` never measures emitted
556
- bytes, a truncated return does not suppress delivered blocks, and blocks do not
557
- shrink the return budget.
691
+ **M3.** Text emission shares `R2`'s 24,000 serialized-character cap. Its
692
+ serialized block size reduces the return budget, and oversized text fails with
693
+ `result_too_large`. Image/audio blocks use the separate transport budget and
694
+ reach the model as real MCP media content, including when `emit` is not awaited.
695
+ The trusted runner waits for emission acknowledgments before returning success.
696
+ An unobserved emission failure fails the run; an awaited failure remains catchable.
697
+ Rejections propagated through `then` or `finally` can be caught later in that
698
+ chain. A separate unhandled branch still fails the run.
558
699
 
559
700
  **M4.** A failed program delivers no blocks. The error result reports
560
- `emittedDiscarded: N` when N > 0 — a field on the structured envelope, a
561
- trailing line on the plain-text paths — never silently.
701
+ `emittedDiscarded: N` when N > 0 as a field on the structured envelope.
562
702
 
563
703
  **M5.** Two budgets (`ConnectaConfig.execute.maxEmittedBytes` /
564
704
  `.maxEmittedBlocks`, defaults 4,000,000 serialized bytes and 32 blocks) fail
565
705
  loudly at the `emit` call, naming the budget and the room remaining; nothing is
566
- partially accepted and prior blocks stand. No `get_result` stash: the program
706
+ partially accepted and prior blocks stand. Accepted and rejected emission attempts
707
+ also have a terminal limit of four times the configured block cap (at least 128),
708
+ which bounds retained host failures and guest acknowledgement state without
709
+ discarding an older error identity. No result stash for emitted blocks: the program
567
710
  learns while it can still choose differently. The byte default is a transport
568
711
  bound, not a context bound — emitted media reaches the model as media, not
569
712
  base64 text.
@@ -573,13 +716,12 @@ exactly as much as the return value. Preservation is re-emission of the raw
573
716
  downstream block, so `S5`'s uncapped fallthrough is contract.
574
717
 
575
718
  **M7.** `emit` alone spends no host-call budget (`L4`); `search`, `describe`,
576
- and `call` share it. `M5`'s bounds are emission's only bounds.
719
+ `call`, `read`, `result`, and `skill` share it. Text also observes `R2`.
577
720
 
578
- **M8.** Emission asks nothing of an executor: `emit` is a provider function,
579
- blocks cross the guest boundary once as an argument, and `ExecuteResult` is
580
- unchanged — `Executor` stays assignable from `@cloudflare/codemode`'s
581
- `DynamicWorkerExecutor`, so any executor that bridges provider calls gets
582
- emission for free.
721
+ **M8.** `emit` is a provider function; blocks cross the guest boundary once as
722
+ an argument. `ExecuteResult` remains compatible with upstream codemode's types;
723
+ construction requires the Worker adapter or explicit custom opt-in. Any executor
724
+ that bridges provider calls gets emission for free.
583
725
 
584
726
  **M9.** Request-local and unstreamed: blocks exist only in the finished
585
727
  response, and `emit` resolving means "accepted," never "delivered."
@@ -607,7 +749,7 @@ host-call budget, so an unchecked loop converts a transient failure into
607
749
  Dynamic-Worker-only wait would spend the run's wall clock on code that fails
608
750
  on QuickJS. Return the failure and let the model, which can wait, re-issue
609
751
  with `retryAfterMs` in hand.
610
- - a cancelled or timed-out *execution*: it is already over (`L1`).
752
+ - a cancelled or timed-out _execution_: it is already over (`L1`).
611
753
 
612
754
  **Y4.** A provider's `retryAfterMs` is returned unchanged; the caller decides
613
755
  whether and when to reissue, and a later call gets its own deadline and
@@ -620,14 +762,21 @@ poll, no cancellation exception to catch, and no guarantee that a `finally`
620
762
  block runs — a cancelled QuickJS child is terminated outright. Write programs
621
763
  that need no cleanup.
622
764
 
623
- **L2.** What cancellation guarantees: in-flight host calls abort, no further
624
- host call is admitted — `search` and `describe` included, which fail
625
- `cancelled` rather than go on loading catalogs — the response returns
626
- `executor_cancelled` without waiting for the executor to settle, or for an
627
- `acquire()` that ignores the signal to grant, the admission lease is released
628
- (a lease granted after the run gave up is released on arrival), and nothing
629
- request-bound survives the request. Whether the program itself stops is the
630
- executor's (`X3`).
765
+ **L2.** Cancellation aborts in-flight host calls and admits no further calls,
766
+ including discovery. The response returns `executor_cancelled` without awaiting
767
+ a wedged executor or `acquire()` that ignores its signal. It releases the lease;
768
+ a late-granted lease releases on arrival. Nothing request-bound survives the
769
+ request. Whether guest computation stops is the executor's (`X3`).
770
+
771
+ Programs and direct calls use the same invocation deadline and stages. A call's
772
+ `timeoutMs` wins, then `calls.defaultTimeoutMs`, then `execute.hostCallTimeoutMs`
773
+ (default 15,000). A guest may request a longer individual call, but the sandbox
774
+ and request ceilings still bound the whole run. Timeout details are agent-facing;
775
+ INV-6 operator records retain only checked classifications and numeric facts.
776
+ A dispatched write timeout becomes `write_outcome_unknown` and retains its
777
+ deadline details even when the program catches the failure. The final program
778
+ error includes the first timeout in `details`; multiple write timeouts also
779
+ appear in `timeouts`, bounded to ten entries.
631
780
 
632
781
  **L3.** Every execution runs under a wall-clock deadline that includes time
633
782
  spent waiting on host calls. Expiry ends the run with an execution error and no
@@ -636,7 +785,7 @@ it sits a ceiling connecta enforces outside the sandbox, `execute.watchdogMs`
636
785
  (120 s by default, above both executors' own deadlines), because an executor's
637
786
  deadline may live inside the sandbox it is meant to stop: a wedged Dynamic
638
787
  Worker never fires its in-isolate timer. A run whose executor has not settled
639
- by the ceiling ends as a non-retryable `executor_failed` whose message calls
788
+ by the ceiling ends as a non-retryable `timeout` whose message calls
640
789
  the sandbox unresponsive, with no partial result, and its admission lease is
641
790
  released, so wedged runs cannot fill the code pool. Raising an executor's
642
791
  deadline past the ceiling means raising the ceiling too.
@@ -644,25 +793,38 @@ deadline past the ceiling means raising the ceiling too.
644
793
  **L4.** Per-execution bounds that are contract, identical in both executors
645
794
  because connecta enforces them above the sandbox:
646
795
 
647
- | Bound | Value |
648
- | --- | --- |
649
- | Host calls per execution, shared by `search`, `describe`, and `call` | 20 by default, `execute.maxHostCalls` |
650
- | Exempt writes per run, on top of the host calls they also spend (`W10`) | 10 by default, `execute.maxWrites` |
651
- | Deadline per host call | 15 s, `execute.hostCallTimeoutMs`; one deadline covers catalog resolution, admission, and the connector call |
652
- | Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
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
- | Result | 24,000 serialized characters |
657
- | Logs presented to the model | 4,000 characters |
658
-
659
- Every `call` attempt spends one host call on entry, before address resolution,
660
- catalog lookup, safety checks, validation, or dispatch, so unknown addresses,
661
- unknown tools, catalog failures, and other pre-dispatch refusals cost what a
662
- successful call costs; catching a refusal does not refund it. `search` and
663
- `describe` likewise spend on entry. Exhausting the budget fails that call with
664
- non-retryable `budget_exceeded` (`E2`) and a message naming the budget. No
665
- connector is reached, and the budget does not refill inside one execution.
796
+ | Bound | Value |
797
+ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
798
+ | Host calls per execution, shared by `search`, `describe`, `call`, and `read` | 20 by default, `execute.maxHostCalls` |
799
+ | Trusted-pool writes per run, on top of the host calls they also spend (`W10`) | 10 by default, `execute.maxWrites` |
800
+ | Deadline per host call | 15 s, `execute.hostCallTimeoutMs`; one deadline covers catalog resolution, admission, and the connector call |
801
+ | Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
802
+ | `describe` addresses | ≤ 100 |
803
+ | `describe` nearby suggestions | ≤ 3 canonical addresses per failed entry |
804
+ | Caller text echoed by `describe` recovery | ≤ 512 UTF-8 bytes per field, plus `…` |
805
+ | Result | 24,000 serialized characters |
806
+ | Logs presented to the model | 4,000 characters |
807
+
808
+ Every `call`, `read`, `search`, and `describe` spends one host call on entry, before
809
+ resolution, validation, or dispatch. Catching a local refusal refunds nothing.
810
+ The first call beyond the budget ends the host run with one non-retryable
811
+ `budget_exceeded` (`E2`). Its bridge stays pending until lease disposal so guest
812
+ `try/catch` cannot swallow it. Later host access, including `emit`, stops; pending
813
+ replies are withheld. The refusal reaches no connector, returns no partial result,
814
+ and discards accepted emits (`M4`). Calls still resolving or awaiting admission
815
+ are cancelled, including trusted-pool writes past their gate. Dispatched trusted-pool writes
816
+ drain under `W9`; unknown outcomes retain precedence. The lease then releases:
817
+ QuickJS terminates its child, and the Worker adapter disposes RPC and loader
818
+ handles independently of the guest deadline. Workers guest computation may
819
+ continue briefly, including a timer escaping the bridge (`X3`), with no host
820
+ access. The Promise executor contract carries typed failures separately from guest values.
821
+
822
+ The failure adds payload-free `hostCalls: { attempted, admitted, succeeded, failed }`
823
+ without diagnostics. `attempted` includes the first refusal, normally 21; `admitted`
824
+ counts the 20 calls past the gate, local refusals included. `succeeded` and `failed`
825
+ count outcomes at response time, the budget refusal and abandoned pending calls
826
+ as failed, so `succeeded + failed === attempted`. `emit` and later attempts are
827
+ excluded; no budget refills.
666
828
 
667
829
  **L5.** The guest is memory-, stack-, and CPU-bounded, and a program that
668
830
  exhausts a bound ends the run with an error instead of degrading the host. The
@@ -676,18 +838,19 @@ code safe to run at all.
676
838
  bounded — QuickJS caps both at 256 KiB (`X10`) — and exceeding either fails that
677
839
  call, not the execution, so a program can catch it and ask for less. The failure
678
840
  is executor-owned untyped text, not a Connecta host failure (`E1`). An
679
- over-bound *result* names the address the program called rather than only the
680
- generic bridge function; an over-bound *argument* payload is refused before it
841
+ over-bound _result_ names the address the program called rather than only the
842
+ generic bridge function; an over-bound _argument_ payload is refused before it
681
843
  is parsed, so it names no address at all.
682
844
 
683
845
  **L7.** Executions are admitted, not queued indefinitely: bounded concurrency
684
846
  plus a bounded queue with a wait timeout. Overload is a retryable
685
847
  `executor_overloaded` carrying `retryAfterMs`; cancellation and shutdown are
686
- terminal. Admission happens *before* any catalog or provider is built, so a
848
+ terminal. Admission happens _before_ any catalog or provider is built, so a
687
849
  queued request holds no state.
688
850
 
689
- **L8.** Bounds are deployment configuration, not program inputs: a program
690
- cannot raise one by asking. `execute_code`'s description states the host-call
851
+ **L8.** Result, log, host-call, and emission budgets are deployment configuration.
852
+ A call may request `timeoutMs` just as a direct call does; the guest deadline is
853
+ capped by the time remaining before the program watchdog. `execute_code`'s description states the host-call
691
854
  budget and the per-call deadline — the ones a program must plan around before it
692
855
  runs. The result and log caps live here and in the truncation notice itself
693
856
  (`R2`, `R5`).
@@ -705,7 +868,7 @@ event.
705
868
 
706
869
  **V2.** Each event carries `connectorId`, `toolName`, `address`, `source`,
707
870
  `outcome` (`success`, `error`, `timeout`, `cancelled`), `durationMs`,
708
- `attempts`, and `errorCode` when the call *failed* — plus request, actor, and
871
+ `attempts`, and `errorCode` when the call _failed_ — plus request, actor, and
709
872
  server identity. Rows written before
710
873
  [#672](https://github.com/zackbart/connecta/issues/672) may also carry the
711
874
  `paused` and `approved` outcomes, `source: "resume_execution"`, and an
@@ -719,65 +882,80 @@ is still recorded. `address` is canonical (`A1`) where a tool resolved,
719
882
  otherwise the name the program used — the honest record of what was attempted.
720
883
 
721
884
  **V3.** A call whose connector does not exist is recorded at the address as
722
- written, *provided* it split into the two fields activity keeps — one with no
885
+ written, _provided_ it split into the two fields activity keeps — one with no
723
886
  interior dot records nothing. An invented id is the address mistake an operator
724
887
  most needs to see, but recording it as written puts caller-authored text in
725
888
  fields that are otherwise operator- and connector-authored, so `connectorId`
726
889
  and `toolName` clamp at 128 UTF-8 bytes (`address` at 257) with a `…` marker:
727
- payload-free *by construction* means the event has nowhere to put a payload.
890
+ payload-free _by construction_ means the event has nowhere to put a payload.
728
891
 
729
892
  **V4.** The execution itself emits no event. It has no address, and its one
730
- distinctive artifact is the program source — exactly what a payload-free history
893
+ distinctive output is the program source — exactly what a payload-free history
731
894
  must never keep.
732
895
 
733
- **V5.** An exempt write a program issues after it has already returned is not
896
+ **V5.** A trusted-pool write a program issues after it has already returned is not
734
897
  sent and records nothing: it was never an attempt (`W9`).
735
898
 
736
899
  ## Writes
737
900
 
738
- A program does not ask anyone anything. It runs explicitly read-only tools and
739
- the writes config exempts from asking, and refuses every other write (`E4`), so
740
- each consequential call is its own `call_destructive_tool` and the host's
741
- permission prompt is the one approval there is. Programs once paused at a write
742
- and resumed by replay
743
- ([#565](https://github.com/zackbart/connecta/issues/565)); hosts approve one
744
- call at a time, so under always-allow that prompt approved nothing and cost a
745
- round trip and a replay, and it went
746
- ([#672](https://github.com/zackbart/connecta/issues/672)). `W1`–`W8` and `W11`
747
- belonged to pausing and stay retired; nothing reuses those ids.
748
-
749
- **W9.** An exempt write the program does not await outlives it: the play stops
750
- gating once the program settles — a write that reaches the gate after is not
751
- sent (`V5`) — and waits for writes already dispatched, each within its host-call
752
- deadline, before the run's scope aborts them. A write sent and never answered
753
- makes the result `write_outcome_unknown` with
754
- `writes: { succeeded, failed, unknown }`, whatever the program returned, and it
755
- is never sent again. Only a refusal (`auth_required`, `invalid_args`,
756
- `not_found`, `rate_limited`, `conflict`, `input_required_unsupported`) or the
757
- tool's own `isError` answers; an `isError` reading as a timeout does not, and
758
- `connector_call_failed` — an oversized body, a refused redirect — may come after
759
- the write landed. A program that fails after writing carries the same `writes`
760
- on its error, because it is not a program that is safe to run again.
761
-
762
- **W10.** `execute.maxWrites` (default 10) bounds a run's exempt writes beyond
763
- the host calls they spend, checked at the gate after validation and before
764
- admission, so an over-budget write fails `budget_exceeded` without a permit.
765
- The host-call budget bounds fan-out; this one bounds how much a program may
766
- change unasked, which is a different question even for writes a deployment
767
- called cheap.
768
-
769
- **W12.** Config may exempt a write from asking, and nothing else may
770
- ([#566](https://github.com/zackbart/connecta/issues/566)). `execute.approval`
771
- maps connector ids and `connector.tool` addresses to `"never"` or `"ask"`; the
772
- address wins over the connector entry, which wins over a connector's own
773
- built-in default (reserved for connectors connecta ships, such as artifacts),
774
- which wins over `"ask"`. An exempt call is decided at the write gate, after
775
- validation and before admission, and dispatches with the write budget (`W10`),
776
- `W9`'s tracking, and a `V1` event. Exempt is never read-only: no read-only tool
777
- is reported exempt, discovery keeps an exempt tool approval-required,
778
- `call_tool` refuses it, and no annotation grants it, or a downstream could
779
- exempt itself. Unknown connectors and `api()` addresses its tools lack refuse
780
- to construct; an unserved remote address never matches.
901
+ Pool trust belongs to deployment code. The default `/mcp` endpoint uses
902
+ `trust: "read-only"`; each named pool has its own `trust`, also defaulting to
903
+ `"read-only"`. A trusted default endpoint does not make named pools trusted.
904
+ The default preserves refusal of ordinary program writes. A deployment must opt into program writes explicitly.
905
+
906
+ ```ts
907
+ createConnecta({
908
+ connectors, executor,
909
+ trust: "read-only",
910
+ pools: {
911
+ automation: { tools: ["posthog"], trust: "trusted", grant },
912
+ },
913
+ classification: {
914
+ posthog: { exec: "read" }, // only for a deployment with a read-only exec contract
915
+ vendor: { mislabeled_update: "write" },
916
+ },
917
+ });
918
+ ```
919
+
920
+ The registry computes one `classifyTool` verdict on each request-scoped
921
+ published catalog entry. It caches and persists only raw downstream facts.
922
+ Every caller consumes that stored verdict. Config cannot classify an individual
923
+ invocation's arguments: marking a mixed entry point read is an assertion about
924
+ all calls this deployment can make through it.
925
+
926
+ | Precedence | Evidence | Result |
927
+ | ---------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
928
+ | 1 | Exact deployment `classification[connectorId][toolName]` | `read` or `write`, even over a stale provider review |
929
+ | 2 | `Connector.classification` provider review | Reviewed writes remain writes; a stale schema digest makes a read a write; an explicit downstream contradiction also invalidates a reviewed read |
930
+ | 3 | Downstream annotations | Read only when `readOnlyHint === true && destructiveHint !== true`; otherwise write |
931
+
932
+ Unknown connector override keys fail construction. Unknown static tool keys
933
+ fail construction; unknown remote tool keys fail publication of the whole
934
+ catalog. Overrides are exact tool names, including names containing dots.
935
+ They are never inferred from descriptions or arguments.
936
+
937
+ **W9.** Programs in trusted pools may write. A dispatched write is awaited even
938
+ if the program does not await it, bounded by its host-call deadline. A write
939
+ that reaches accounting after the program settles is not sent. An unanswered
940
+ write makes the run fail `write_outcome_unknown` with
941
+ `writes: { succeeded, failed, unknown }`, regardless of its return value.
942
+ A failed program that already wrote carries those counts on its error. No
943
+ write is automatically retried, including an ambiguous timeout. A downstream
944
+ refusal or a tool's `isError` can establish failure; a timeout or an unanswered
945
+ transport failure leaves the outcome unknown.
946
+
947
+ **W10.** `execute.maxWrites`, default 10, bounds writes in trusted programs in
948
+ addition to the host-call budget. It is checked after argument validation and
949
+ before admission, so an over-budget write spends no permit.
950
+
951
+ **W12.** `trusted` endpoints annotate `execute_code` as a write and permit
952
+ program writes. `read-only` endpoints annotate it as read-only and refuse
953
+ program writes before validation, with `call_destructive_tool` as the recovery.
954
+ `call_tool` accepts only reads in either tier; `call_destructive_tool` may send
955
+ writes in either tier. Approval belongs to the host. Trust grants no tool or
956
+ connector access and cannot be set by guest code, request arguments, or a
957
+ downstream catalog. `execute.approval`, `Connector.approval`, and per-tool
958
+ approval exemptions are removed. `W1`–`W8` and `W11` remain retired.
781
959
 
782
960
  ## Executor exceptions
783
961
 
@@ -796,12 +974,13 @@ isolate limits apply untuned. A specific heap ceiling is a Node-only option.
796
974
  `AbortSignal` and kills the child — SIGTERM, then SIGKILL if it has not exited
797
975
  within a second — and releasing a lease whose child is still running recycles
798
976
  that child, so a run the watchdog abandons (`L3`) ends too.
799
- The Dynamic Worker executor's `execute()` takes no signal, so a cancelled or
800
- abandoned program runs on in its isolate until its host calls fail or its own
801
- deadline expires. `L2` and `L3` hold either way — the calls abort, connecta
802
- stops awaiting the executor and frees the admission slot — but "the run ends"
803
- is best-effort on Workers, and for that tail the code pool bounds what
804
- connecta is waiting on, not what the platform is still running.
977
+ Upstream `execute()` takes no signal; its guest timeout may never settle after
978
+ the parent response ends. `workerExecutor()` owns each lease's RPC and loader
979
+ handles: release disposes them, detaches host functions, and settles the adapter's
980
+ promise independently of upstream evaluation. Guest computation may continue
981
+ briefly until platform teardown, but no late return, callback, `finally`, call,
982
+ or emit changes the finished host result. Disposal releases request resources;
983
+ it does not guarantee a synchronous guest-CPU kill.
805
984
 
806
985
  **X4. Log rendering and capture.** QuickJS JSON-stringifies non-string arguments
807
986
  and captures `log`, `info`, `warn`, `error`, and `debug`; the Dynamic Worker
@@ -820,14 +999,16 @@ starts has no guest logs to recover.
820
999
  `process`, timers, `crypto`, or `WebSocket`, and its Node child starts with an
821
1000
  explicit environment holding only `TZ=UTC`, so local time matches a Dynamic
822
1001
  Worker's, rather than inheriting
823
- deployment variables or `NODE_OPTIONS`. A Dynamic Worker has those globals plus a non-contract set of
824
- runtime builtins through `import()` and `process.getBuiltinModule()`, including
825
- `node:path`, `node:crypto`, `node:net`, `node:tls`, `node:dns`, `node:module`,
826
- and `cloudflare:workers`; the upstream set drifts, so that is not an allowlist.
827
- The supported construction is exactly `new DynamicWorkerExecutor({ loader })` —
1002
+ deployment variables or `NODE_OPTIONS`. A Dynamic Worker exposes runtime-only
1003
+ builtins through `import()` and `process.getBuiltinModule()`, including `node:path`,
1004
+ `node:crypto`, `node:net`, `node:tls`, `node:dns`, and
1005
+ `cloudflare:workers`; the upstream set drifts, so that is not an allowlist.
1006
+ The adapter refuses `node:module` and `node:process` imports and removes their
1007
+ builtin lookup routes, so `createRequire` cannot resolve runner modules.
1008
+ The supported adapter uses `new DynamicWorkerExecutor({ loader, timeout })`.
828
1009
  `bindings`, `modules`, and `globalOutbound` each grant ambient configuration,
829
- code, or egress. Under it, `process.env`, lexical `this.env`, and
830
- `cloudflare:workers.env` are empty; `node:fs`, `node:http`, and `node:https` are
1010
+ code, or egress. Under it, `process.env` and `cloudflare:workers.env` are empty,
1011
+ and lexical `this.env` is undefined; `node:fs`, `node:http`, and `node:https` are
831
1012
  unavailable through either access route; external `fetch`, `WebSocket`,
832
1013
  `node:net`, and `node:tls` fail with workerd's outbound-denial error; DNS lookup
833
1014
  ends unresolved; and `fetch("data:...")` resolves locally. `P2` is the portable
@@ -844,9 +1025,9 @@ reasonable demand on a platform sandbox.
844
1025
  values through a tagged envelope, so a `Uint8Array` may survive there. `P3` is
845
1026
  the contract: JSON-serializable values, or the program is Workers-only.
846
1027
 
847
- **X8. Unknown-property message.** An unknown `connecta` property throws
848
- `Unknown function connecta.x` on QuickJS and `Tool "x" not found` on the Dynamic
849
- Worker. Both satisfy `E6`; the text is not contract.
1028
+ **X8. Unknown properties.** The guest namespace exposes only the six documented
1029
+ functions. Unknown and inherited members are absent on both executors; calling
1030
+ one is a guest `TypeError` under `E6`. Engine message text is not contract.
850
1031
 
851
1032
  **X9. Refusing a value outside JSON.** The Dynamic Worker ends the run with an
852
1033
  error when a program returns something its codec cannot carry. QuickJS converts
@@ -867,70 +1048,78 @@ the program either way (`R1`).
867
1048
  **X12** described a paused sandbox and left with pausing
868
1049
  ([#672](https://github.com/zackbart/connecta/issues/672)); the id stays retired.
869
1050
 
870
- **X11. Typed host rejection.** Both executors rebuild Connecta's authenticated
871
- host-failure frame as a thrown guest `Error` (`E1`). The per-run secret stays in
872
- the trusted prelude closure and the prelude locks `globalThis.Error`, so guest
873
- code and connector prose cannot forge the frame. The host bounds details before
874
- framing (`E1`), JSON escapes included; QuickJS refuses an oversized frame whole
875
- rather than slicing through its JSON, hides one whose JSON is malformed, and
876
- keeps the raw bridge and its JSON decoder in a private closure so guest code
877
- cannot intercept a frame first. A mismatched frame is ordinary untyped prose.
1051
+ **X11. Typed host rejection.** QuickJS creates and retains guest Error handles
1052
+ in its host bridge and compares handles when the program rejects. The Worker
1053
+ adapter stores each typed failure in a per-run host map under a random UUID.
1054
+ Its private entrypoint associates that ID with the guest Error and returns only
1055
+ the ID through Worker Loader RPC. The host resolves it to its own record before
1056
+ upstream result shaping; unknown or forged IDs attach no typed details, and IDs
1057
+ from previous runs cannot resolve. Rethrowing an earlier call's Error retains
1058
+ that call's record. The guest receives a separate copy for catching errors.
1059
+ Both construct `failure.call` from host-retained values, never from a guest
1060
+ result, parsed guest-realm object, or printed text. The Worker codec checks own
1061
+ binary-envelope properties, and completion uses captured array iteration even
1062
+ when the executor runs without the Connecta guest prelude. Host failure details are
1063
+ bounded before bridging (`E1`), including JSON escapes. No frame parser or
1064
+ prose matching participates in classification. The guest cannot import runner
1065
+ modules or repeat privileged initialization. QuickJS checks host interrupt and
1066
+ deadline facts before describing a rejection. It retains a private native Error
1067
+ brand check and reads only own string data descriptors for diagnostics; guest
1068
+ accessors, serialization hooks, and Proxy traps do not run. Other thrown objects
1069
+ receive a fixed description.
878
1070
 
879
1071
  ## Verification
880
1072
 
881
- Every clause has a test. `test/guest-contract-cases.ts` holds the case table,
882
- written once and run twice: `test/guest-api-contract-quickjs.test.ts` runs it on
883
- the Node QuickJS executor, and `test/guest-api-contract.test.ts` runs it on a
884
- real `DynamicWorkerExecutor` in workerd — a Miniflare Worker Loader binding
885
- makes that arm real rather than simulated — alongside the clauses connecta
886
- enforces above any executor. Rows naming `test/guest-api-contract.test.ts` are
887
- covered by both arms, and each case's title carries its clauses. Two arms
888
- passing one table is also the check on the executor duties above, with
889
- `test/codemode-compat.test.ts` holding the upstream `Executor` shape assignable.
890
-
891
- | Clauses | Test |
892
- | --- | --- |
893
- | `P1`, `P5` | both guest-contract executors (TypeScript syntax, trailing terminators, wrapper recovery), `test/program-source.test.ts` (which semicolons and wrappers are recovered), `test/quickjs-executor.test.ts` (`normalizeCode`) |
894
- | `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/quickjs-child-stderr.test.ts` (a child-process environment holding only `TZ=UTC`), `test/deployment-shapes.test.ts` (loader-only Worker construction) |
895
- | `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
896
- | `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
897
- | `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (canonical addressing), `test/server.test.ts` (bounded live connector inventory) |
898
- | `S1`, `S1a`, `S2` | `test/guest-api-contract.test.ts` (flat page, connector guides, schema keys, unfiltered browse), `test/execute.test.ts` (guide pagination/partial/no-match behavior and `$ref`/`allOf`), `test/meta-tools-search.test.ts` (mixed complete/partial ranking, stable pagination, the two safety classes), `test/typescript-signatures.test.ts` (the TypeScript format over provider and pathological schemas, search/describe parity, observed labeling), `test/typescript-signatures-parse.test.ts` (every rendering parses) |
899
- | `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
900
- | `S4` | both guest-contract executors (ordered mixed describe results with unknown-address, unknown-tool suggestion, and catalog-failure details), `test/meta-tools-search.test.ts` (top-level routing, no-suggestion, catalog-failure, and hostile-input bounds) |
901
- | `S5`, `S6` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`, fail-closed annotations, activity parity) |
902
- | `S7` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (parallel calls and shared admission) |
903
- | `S8`, `E1`, `X11` | both guest-contract executors (caught call, discovery, utility, budget, removed-function, and forgery cases; typed promise rejections), `test/quickjs-executor.test.ts` (oversized messages, private transport, forged frames) |
904
- | `S9` | `test/result-shapes.test.ts` (value exclusion, bounds, merging, LRU and time expiry, runtime isolation, read-only admission, declared precedence, definition invalidation, unwrapped MCP results, discovery provenance, copy isolation, failure isolation) |
905
- | `E2`, `E8` | `test/guest-api-contract.test.ts` (code → `retryable`, caught, parallel, and uncaught validation recovery, a conflict's bounded `current`), `test/meta-tools-call.test.ts` (direct, destructive, provider fallback), `test/validate.test.ts` (bounded payload-free findings), `test/errors.test.ts` |
906
- | `E3`, `E4` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`, destructive reroute), `test/program-writes.test.ts` (the refusal's `nextAction`, nothing sent, a caught refusal) |
907
- | `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) |
908
- | `E6`, `X8` | `test/guest-api-contract.test.ts` (unknown and inherited members, wrapped-message precedence), `test/quickjs-executor.test.ts` |
909
- | `E7` | `test/guest-api-contract.test.ts` (refusals about a `503`-named connector), `test/errors.test.ts` |
910
- | `R1`, `R2`, `R3` | `test/guest-api-contract.test.ts` (pass-through, truncation is success, envelope fits the cap and is idempotent) |
911
- | `R4`, `M6`, `M9` | verdicts; `R2`'s guard, `M1`'s strict typing, and `M2`'s collect-then-deliver are their enforcement |
912
- | `R5` | `test/guest-api-contract.test.ts`, `test/quickjs-log-limits.test.ts` |
913
- | `R6`–`R8` | `test/guest-api-contract.test.ts` (normal result keys), `test/execute.test.ts` (opt-in operation aggregates, failure paths, payload exclusion) |
914
- | `Y1`, `Y2`, `Y3` | `test/guest-api-contract.test.ts` (one attempt per call, retryable flags by code) |
915
- | `Y4` | `test/meta-tools-call.test.ts`, `test/call-admission.test.ts` (one attempt, retry hints, caller reissue) |
916
- | `L1`, `L2` | `test/guest-api-contract.test.ts` (in-flight call fails `cancelled`), `test/execute.test.ts` (cancels outstanding host calls, discovery included, and refuses discovery after the run; a cancelled wedged executor, or one whose `acquire()` ignores the signal, returns promptly and releases its lease) |
917
- | `L3`, `X1` | `test/guest-api-contract.test.ts` (short-deadline executors), `test/execute.test.ts` (the watchdog ends a never-settling executor, frees the default pool, spares a slow run, and falls back from an unusable value) |
918
- | `L4`, `L8` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (shared discovery/call budgets) |
919
- | `L5`, `L7`, `X2` | `test/quickjs-executor.test.ts` (CPU, heap), `test/execute.test.ts` and `test/executor-admission.test.ts` (bounded admission and queue) |
920
- | `L6`, `X10` | `test/quickjs-executor.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message), `test/quickjs-child-stderr.test.ts` (outer reply serialization failure settles the call) |
921
- | `V1`–`V4` | `test/guest-api-contract.test.ts` (dispatched calls, every refusal class including an address no connector owns, the friction each derives, no event for the execution itself), `test/activity.test.ts` (the shared code → friction table, the identity clamp, the one-attempt floor), `test/operator-view.test.ts`, `test/d1-activity-example.test.ts` (historical pause and approval rows still render and round-trip) |
922
- | `V5`, `W9` | `test/program-writes.test.ts` (an unawaited exempt write finished and recorded, its unknown outcome reported, counts on a failed program, the classification table), `test/invocation-pipeline.test.ts` (the gate after validation, an unrecorded refusal) |
923
- | `W10`, `W12` | `test/program-writes.test.ts` (an exempt write runs and every other write keeps `E4`, the write budget, precedence and a connector default switched off, `call_tool` still refusing, search and describe markers, construction refusals), `test/artifacts-connector.test.ts` (the shipped default and switching it off), `test/operator-ui-model.test.ts`, `test/browser/operator-ui.spec.ts` (the badge) |
924
- | `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (every rejected shape) |
925
- | `M2`, `M3` | `test/guest-api-contract.test.ts` (delivery order, truncated return plus delivered blocks), `test/execute-emit.test.ts` (envelope, `structuredContent`, byte-for-byte no-emit path) |
926
- | `M4` | `test/guest-api-contract.test.ts` (discard is visible), `test/execute-emit.test.ts` (structured and plain paths), `test/quickjs-executor.test.ts` (mid-run shutdown) |
927
- | `M5`, `M7` | `test/execute-emit.test.ts` (both budgets fail the crossing block; host-call budget untouched) |
928
- | `M8` | two arms passing one case table, `test/codemode-compat.test.ts` |
929
- | `M10` | `test/execute-emit.test.ts` (aggregate present, numbers only, absent when nothing emitted) |
930
- | `X4` | `test/guest-api-contract.test.ts` (string logs only), `test/quickjs-executor.test.ts` (logs before cancellation), `test/quickjs-child-stderr.test.ts` (crash, shutdown, deadline, IPC failure, bounded parent retention), `test/quickjs-log-limits.test.ts` (unchanged successful logs) |
931
- | `X3`, `X6` | `test/quickjs-executor.test.ts` (cancels a running child, never-settling await), `test/execute.test.ts` (a wedged executor stops being awaited) |
932
- | `X7` | `P3`'s tests; the Workers superset is deliberately unused |
933
-
934
- The surface itself is checked by `test/server.test.ts` (the exact seven-tool list),
1073
+ `test/guest-contract-cases.ts` runs on QuickJS in `test/guest-api-contract-quickjs.node.test.ts`
1074
+ and on the Worker adapter with a real Miniflare Loader in `test/guest-api-contract.test.ts`.
1075
+ Titles name clauses; executor-independent clauses run in both arms.
1076
+ `test/codemode-compat.test.ts` pins upstream shape compatibility, minified upstream
1077
+ rejection, and branded adapter acceptance across module copies.
1078
+
1079
+ | Clauses | Test |
1080
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1081
+ | `P1`, `P5` | both guest-contract executors (TypeScript syntax, trailing terminators, wrapper recovery), `test/program-source.test.ts` (which semicolons and wrappers are recovered), `test/quickjs-executor.node.test.ts` (`normalizeCode`) |
1082
+ | `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.node.test.ts` (exact absent globals and blocked imports), `test/quickjs-child-stderr.node.test.ts` (a child-process environment holding only `TZ=UTC`), `test/deployment-shapes.node.test.ts` (loader-only Worker construction) |
1083
+ | `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
1084
+ | `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
1085
+ | `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (canonical addressing), `test/server.test.ts` (bounded live connector inventory) |
1086
+ | `S1`, `S1a`, `S2` | `test/guest-api-contract.test.ts` (flat page, connector guides, schema keys, unfiltered browse), `test/execute.test.ts` (guide pagination/partial/no-match behavior and `$ref`/`allOf`), `test/meta-tools-search.test.ts` (mixed complete/partial ranking, stable pagination, the two safety classes), `test/typescript-signatures.test.ts` (the TypeScript format over provider and pathological schemas, search/describe parity, observed labeling), `test/typescript-signatures-parse.node.test.ts` (every rendering parses) |
1087
+ | `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
1088
+ | `S4` | both guest-contract executors (ordered mixed describe results with unknown-address, unknown-tool suggestion, and catalog-failure details), `test/meta-tools-search.test.ts` (top-level routing, no-suggestion, catalog-failure, and hostile-input bounds) |
1089
+ | `S5`, `S6` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`, fail-closed annotations, activity parity) |
1090
+ | `S7` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (parallel calls and shared admission) |
1091
+ | `S8`, `E1`, `X11` | both guest-contract executors (caught call, discovery, utility, removed-function, and forgery cases; typed promise rejections), `test/quickjs-executor.node.test.ts` (oversized messages, private transport, forged outcomes) |
1092
+ | `S9` | `test/result-shapes.test.ts` (value exclusion, bounds, merging, LRU and time expiry, runtime isolation, read-only admission, declared precedence, definition invalidation, unwrapped MCP results, discovery provenance, copy isolation, failure isolation) |
1093
+ | `S10` | `test/resource-read.test.ts` (qualified routing, whole-connector grants, personal credentials, redaction, admission, timeout, cancellation and downstream MCP reads), both guest-contract executors (successful resource reads and caught/uncaught typed failures) |
1094
+ | `E2`, `E8` | `test/guest-api-contract.test.ts` (code → `retryable`, caught, parallel, and uncaught validation recovery, a conflict's bounded `current`), `test/meta-tools-call.test.ts` (direct, destructive, provider fallback), `test/validate.test.ts` (bounded payload-free findings), `test/errors.test.ts` |
1095
+ | `E3`, `E4` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`, destructive reroute), `test/program-writes.test.ts` (the refusal's `nextAction`, nothing sent, a caught refusal) |
1096
+ | `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.node.test.ts` (mid-run shutdown) |
1097
+ | `E6`, `X8` | `test/guest-api-contract.test.ts` (unknown and inherited members, retained error identities), `test/quickjs-executor.node.test.ts` |
1098
+ | `E7` | `test/guest-api-contract.test.ts` (refusals about a `503`-named connector), `test/errors.test.ts` |
1099
+ | `R1`, `R2`, `R3` | `test/guest-api-contract.test.ts` (pass-through, truncation is success, envelope fits the cap and is idempotent) |
1100
+ | `R4`, `M6`, `M9` | verdicts; `R2`'s guard, `M1`'s strict typing, and `M2`'s collect-then-deliver are their enforcement |
1101
+ | `R5` | `test/guest-api-contract.test.ts`, `test/quickjs-log-limits.node.test.ts` |
1102
+ | `R6`–`R8` | `test/guest-api-contract.test.ts` (normal result keys), `test/execute.test.ts` (opt-in operation aggregates, failure paths, payload exclusion) |
1103
+ | `Y1`, `Y2`, `Y3` | `test/guest-api-contract.test.ts` (one attempt per call, retryable flags by code) |
1104
+ | `Y4` | `test/meta-tools-call.test.ts`, `test/call-admission.test.ts` (one attempt, retry hints, caller reissue) |
1105
+ | `L1`, `L2` | `test/guest-api-contract.test.ts` (in-flight call fails `cancelled`), `test/execute.test.ts` (cancels outstanding host calls, discovery included, and refuses discovery after the run; a cancelled wedged executor, or one whose `acquire()` ignores the signal, returns promptly and releases its lease) |
1106
+ | `L3`, `X1` | `test/guest-api-contract.test.ts` (short-deadline executors), `test/execute.test.ts` (the watchdog ends a never-settling executor, frees the default pool, spares a slow run, and falls back from an unusable value) |
1107
+ | `L4`, `L8` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (shared discovery/call budgets and terminal catch-and-continue loops and queued-write cancellation on both executors), `test/worker-budget-response.node.test.ts` (native handle disposal and admission recovery across completed HTTP responses) |
1108
+ | `L5`, `L7`, `X2` | `test/quickjs-executor.node.test.ts` (CPU, heap), `test/execute.test.ts` and `test/executor-admission.test.ts` (bounded admission and queue) |
1109
+ | `L6`, `X10` | `test/quickjs-executor.node.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message), `test/quickjs-child-stderr.node.test.ts` (outer reply serialization failure settles the call) |
1110
+ | `V1`–`V4` | `test/guest-api-contract.test.ts` (dispatched calls, every refusal class including an address no connector owns, the friction each derives, no event for the execution itself), `test/activity.test.ts` (the shared code → friction table, the identity clamp, the one-attempt floor), `test/operator-view.test.ts`, `test/sql-storage-contract.ts`, run by `test/d1-storage.node.test.ts` and `test/sqlite-storage.node.test.ts` (historical pause and approval rows still render and round-trip) |
1111
+ | `V5`, `W9` | `test/program-writes.test.ts` (an unawaited trusted-pool write finished and recorded, its unknown outcome reported, counts on a failed program, the classification table), `test/invocation-pipeline.test.ts` (the gate after validation, an unrecorded refusal) |
1112
+ | `W10`, `W12` | `test/program-writes.test.ts` (a trusted-pool write runs and every other write keeps `E4`, the write budget, pool trust and override precedence, `call_tool` still refusing, search and describe verdicts, construction refusals), `test/operator-ui-model.test.ts`, `test/browser/operator-ui.spec.ts` (the badge) |
1113
+ | `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (every rejected shape) |
1114
+ | `M2`, `M3` | `test/guest-api-contract.test.ts` (delivery order, truncated return plus delivered blocks), `test/execute-emit.test.ts` (envelope, `structuredContent`, byte-for-byte no-emit path) |
1115
+ | `M4` | `test/guest-api-contract.test.ts` (discard is visible), `test/execute-emit.test.ts` (structured and plain paths), `test/quickjs-executor.node.test.ts` (mid-run shutdown) |
1116
+ | `M5`, `M7` | `test/execute-emit.test.ts` (both budgets fail the crossing block; host-call budget untouched) |
1117
+ | `M8` | two arms passing one case table, `test/codemode-compat.test.ts` |
1118
+ | `M10` | `test/execute-emit.test.ts` (aggregate present, numbers only, absent when nothing emitted) |
1119
+ | `X4` | `test/guest-api-contract.test.ts` (string logs only), `test/quickjs-executor.node.test.ts` (logs before cancellation), `test/quickjs-child-stderr.node.test.ts` (crash, shutdown, deadline, IPC failure, bounded parent retention), `test/quickjs-log-limits.node.test.ts` (unchanged successful logs) |
1120
+ | `X3`, `X6` | `test/quickjs-executor.node.test.ts` (cancels a running child, never-settling await), `test/execute.test.ts` (a wedged executor stops being awaited) |
1121
+ | `X7` | `P3`'s tests; the Workers superset is deliberately unused |
1122
+
1123
+ The surface itself is checked by `test/server.test.ts` (the exact six-tool list),
935
1124
  `test/code-first-surface.test.ts` (construction, executor, removed tools, copy, and size),
936
1125
  and `test/program-writes.test.ts` (the retired pause options refused at construction).