@zackbart/connecta 0.40.0 → 0.42.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 (195) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/bin/connecta.mjs +26 -17
  3. package/bin/meta-tool-names.d.mts +3 -1
  4. package/bin/meta-tool-names.mjs +4 -1
  5. package/bin/version.mjs +1 -1
  6. package/dist/activity.d.ts +32 -5
  7. package/dist/activity.js +32 -6
  8. package/dist/advertised-schema.js +15 -0
  9. package/dist/agent-observation.js +24 -2
  10. package/dist/agent-routes.js +33 -44
  11. package/dist/auth/clerk.d.ts +6 -1
  12. package/dist/auth/clerk.js +16 -0
  13. package/dist/auth/cloudflare-access.js +49 -7
  14. package/dist/auth/downstream-oauth/consent.js +79 -29
  15. package/dist/auth/downstream-oauth/grant-store.js +58 -7
  16. package/dist/auth/downstream-oauth/refresh-coordinator.js +4 -1
  17. package/dist/auth/downstream-oauth/refresh-records.js +6 -5
  18. package/dist/auth/downstream-oauth.js +15 -4
  19. package/dist/auth/oauth-request-not-sent.js +4 -0
  20. package/dist/auth/static-oauth.js +77 -39
  21. package/dist/auth-elicitation.js +118 -61
  22. package/dist/authorization-handoff.js +36 -3
  23. package/dist/catalog-cache.js +352 -43
  24. package/dist/catalog-freshness.js +25 -0
  25. package/dist/catalog-service.js +408 -78
  26. package/dist/catalog.js +216 -62
  27. package/dist/config-defaults.d.ts +2 -1
  28. package/dist/config-defaults.js +4 -1
  29. package/dist/config.d.ts +53 -21
  30. package/dist/config.js +56 -17
  31. package/dist/connectors/remote-mcp/call.js +1 -1
  32. package/dist/connectors/remote-mcp/catalog.js +52 -28
  33. package/dist/connectors/remote-mcp/connection.js +113 -22
  34. package/dist/connectors/remote-mcp/oauth.js +2 -2
  35. package/dist/connectors/remote-mcp/resources.js +5 -2
  36. package/dist/connectors/remote-mcp/transport.js +24 -6
  37. package/dist/connectors/remote-mcp.js +14 -5
  38. package/dist/credential-contract.d.ts +3 -1
  39. package/dist/describe-config.d.ts +4 -2
  40. package/dist/describe-config.js +1 -0
  41. package/dist/discovery-health.js +26 -11
  42. package/dist/downstream-input.js +11 -2
  43. package/dist/errors.d.ts +60 -4
  44. package/dist/errors.js +88 -3
  45. package/dist/execute.js +491 -202
  46. package/dist/executor-admission.js +2 -0
  47. package/dist/executor-contract.js +6 -0
  48. package/dist/executor-result.js +3 -3
  49. package/dist/executors/quickjs-child.js +14 -5
  50. package/dist/executors/quickjs-runtime.d.ts +2 -0
  51. package/dist/executors/quickjs-runtime.js +37 -20
  52. package/dist/executors/quickjs.js +24 -7
  53. package/dist/guarded-transport.js +2 -0
  54. package/dist/guest-functions.js +5 -4
  55. package/dist/guest-runtime.js +37 -1
  56. package/dist/guest-types.d.ts +18 -0
  57. package/dist/http/client.js +14 -2
  58. package/dist/http/errors.js +9 -4
  59. package/dist/http/fields.js +17 -5
  60. package/dist/http/hatch.js +11 -3
  61. package/dist/http/node-transport.js +110 -4
  62. package/dist/http/oauth.js +17 -6
  63. package/dist/http/paging.js +1 -1
  64. package/dist/http/provider.js +2 -6
  65. package/dist/http/vendor.js +6 -1
  66. package/dist/index.d.ts +1 -1
  67. package/dist/index.js +15 -21
  68. package/dist/invocation-failure.js +28 -0
  69. package/dist/invocation.js +167 -64
  70. package/dist/membership.d.ts +1 -1
  71. package/dist/meta-output.js +24 -18
  72. package/dist/meta-tool-names.d.ts +1 -1
  73. package/dist/meta-tool-names.js +1 -1
  74. package/dist/meta-tools.js +286 -73
  75. package/dist/module-contracts.d.ts +3 -1
  76. package/dist/native-recovery.js +4 -1
  77. package/dist/native-tools.js +177 -55
  78. package/dist/node.d.ts +7 -0
  79. package/dist/node.js +85 -4
  80. package/dist/oauth-handoff.js +53 -43
  81. package/dist/operation-activity.js +104 -0
  82. package/dist/operation-vocabulary.js +6 -0
  83. package/dist/operator-record.d.ts +9 -1
  84. package/dist/operator-record.js +16 -0
  85. package/dist/operator-ui/generated.js +2 -2
  86. package/dist/program-result.js +2 -0
  87. package/dist/program-source.js +1 -1
  88. package/dist/program-writes.js +112 -19
  89. package/dist/provider.d.ts +1 -14
  90. package/dist/provider.js +0 -3
  91. package/dist/providers/_shared/google/kit.js +2 -2
  92. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  93. package/dist/providers/breeze/api.js +2 -2
  94. package/dist/providers/breeze/index.js +1 -1
  95. package/dist/providers/breeze/skill.generated.js +1 -1
  96. package/dist/providers/ccb/api.js +2 -2
  97. package/dist/providers/ccb/index.js +1 -1
  98. package/dist/providers/ccb/skill.generated.js +1 -1
  99. package/dist/providers/cloudflare/api.js +2 -2
  100. package/dist/providers/cloudflare/index.js +3 -1
  101. package/dist/providers/cloudflare/skill.generated.js +1 -1
  102. package/dist/providers/docs/index.js +1 -1
  103. package/dist/providers/docs/skill.generated.js +1 -1
  104. package/dist/providers/drive/index.js +1 -1
  105. package/dist/providers/drive/skill.generated.js +1 -1
  106. package/dist/providers/forms/index.js +2 -1
  107. package/dist/providers/forms/skill.generated.js +1 -1
  108. package/dist/providers/github/api.js +98 -6
  109. package/dist/providers/github/index.js +8 -3
  110. package/dist/providers/github/lib/auth.d.ts +3 -1
  111. package/dist/providers/github/lib/auth.js +43 -12
  112. package/dist/providers/github/lib/discovery.js +11 -1
  113. package/dist/providers/github/lib/operations.js +8 -0
  114. package/dist/providers/github/safety.js +39 -0
  115. package/dist/providers/github/skill.generated.js +1 -1
  116. package/dist/providers/github/tools.js +12 -5
  117. package/dist/providers/gmail/index.js +2 -1
  118. package/dist/providers/gmail/skill.generated.js +1 -1
  119. package/dist/providers/granola/index.js +1 -1
  120. package/dist/providers/granola/skill.generated.js +1 -1
  121. package/dist/providers/overflow/index.js +3 -1
  122. package/dist/providers/overflow/skill.generated.js +1 -1
  123. package/dist/providers/planning-center/index.js +12 -6
  124. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  125. package/dist/providers/planning-center/skill.generated.js +1 -1
  126. package/dist/providers/quickbooks/api.js +28 -25
  127. package/dist/providers/quickbooks/index.js +3 -6
  128. package/dist/providers/quickbooks/lib/writes.js +55 -0
  129. package/dist/providers/quickbooks/safety.js +17 -5
  130. package/dist/providers/quickbooks/skill.generated.js +1 -1
  131. package/dist/providers/sheets/index.js +2 -1
  132. package/dist/providers/sheets/skill.generated.js +1 -1
  133. package/dist/providers/slides/index.js +3 -3
  134. package/dist/providers/slides/skill.generated.js +1 -1
  135. package/dist/providers/stripe/index.js +2 -2
  136. package/dist/providers/stripe/skill.generated.js +1 -1
  137. package/dist/providers/tithely/index.js +2 -1
  138. package/dist/providers/tithely/skill.generated.js +1 -1
  139. package/dist/providers/vercel/api.js +3 -2
  140. package/dist/providers/vercel/skill.generated.js +1 -1
  141. package/dist/registry.d.ts +36 -4
  142. package/dist/registry.js +142 -30
  143. package/dist/resource-read.js +21 -2
  144. package/dist/result-preview.js +210 -0
  145. package/dist/result-seal.js +168 -0
  146. package/dist/route-names.js +53 -0
  147. package/dist/routes/activity-context.js +1 -0
  148. package/dist/routes/activity-disclosure.js +26 -4
  149. package/dist/routes/activity.js +2 -1
  150. package/dist/routes/connect.js +21 -13
  151. package/dist/routes/mcp.js +83 -35
  152. package/dist/routes/shared.d.ts +6 -0
  153. package/dist/routes/ui-config.js +1 -1
  154. package/dist/routes/ui-data.js +3 -2
  155. package/dist/runtime/call.js +15 -10
  156. package/dist/runtime/native-storage.js +12 -0
  157. package/dist/runtime/request-reads.js +66 -14
  158. package/dist/runtime/request-scope.js +38 -2
  159. package/dist/runtime/run.js +4 -1
  160. package/dist/sent-secrets.d.ts +20 -0
  161. package/dist/sent-secrets.js +49 -1
  162. package/dist/skill-origin.js +16 -0
  163. package/dist/skills.js +213 -57
  164. package/dist/storage/d1-driver.js +33 -5
  165. package/dist/storage/keys.js +16 -7
  166. package/dist/storage/sql-native.js +60 -3
  167. package/dist/storage/sql.js +6 -0
  168. package/dist/timing-trace.js +84 -0
  169. package/dist/tool-safety.d.ts +33 -0
  170. package/dist/tool-safety.js +51 -15
  171. package/dist/types.d.ts +36 -0
  172. package/dist/url-safety.js +38 -20
  173. package/dist/usage-guide.js +14 -13
  174. package/dist/utility-recovery.js +33 -0
  175. package/dist/validate.js +29 -10
  176. package/dist/version.d.ts +1 -1
  177. package/dist/version.js +1 -1
  178. package/dist/worker.js +60 -15
  179. package/documentation/architecture.md +152 -43
  180. package/documentation/auth.md +160 -53
  181. package/documentation/code-mode.md +396 -119
  182. package/documentation/deploying.md +35 -10
  183. package/documentation/hosted-mcp-migration.md +9 -3
  184. package/documentation/integrating.md +27 -5
  185. package/documentation/meta-tools.md +761 -157
  186. package/documentation/operating.md +91 -4
  187. package/documentation/operator-ui.md +51 -2
  188. package/documentation/providers.md +34 -20
  189. package/examples/worker/README.md +3 -3
  190. package/guides/how-it-works.md +2 -1
  191. package/guides/safety-and-trust.md +1 -3
  192. package/package.json +10 -8
  193. package/templates/node/AGENTS.md +3 -3
  194. package/templates/node/README.md +1 -1
  195. package/templates/node/package.json +1 -1
@@ -34,7 +34,7 @@ new behavior.
34
34
  ## Deploy-time capability
35
35
 
36
36
  The `executor` passed to `createConnecta()` is required. The
37
- [six-tool surface](./meta-tools.md#the-six-tools) is defined in the meta-tool
37
+ [meta-tool surface](./meta-tools.md#the-meta-tools) is defined in the meta-tool
38
38
  reference. Construction fails when the
39
39
  executor is missing, and the removed deployment-level `surface` option is rejected rather than
40
40
  ignored ([#273](https://github.com/zackbart/connecta/issues/273)), as are the
@@ -64,9 +64,9 @@ import { workerExecutor } from "@zackbart/connecta/worker";
64
64
  createConnecta({ executor: workerExecutor({ loader: env.LOADER }), readOnly: false /* … */ });
65
65
  ```
66
66
 
67
- The adapter uses only `loader` and an optional `timeout`. Its admission defaults
68
- are two active runs, eight queued runs, a 60-second queue deadline (the default
69
- guest deadline) and a one-second `retryAfterMs` hint. `admission` replaces all
67
+ The adapter uses only `loader` and an optional `timeout` (default 50 s, `L3`). Its admission defaults
68
+ are two active runs, eight queued runs, a 40-second queue deadline (which leaves a
69
+ queued run time inside the response ceiling, `L3`) and a one-second `retryAfterMs` hint. `admission` replaces all
70
70
  four: `{ concurrency, maxQueueSize, queueTimeoutMs, retryAfterMs? }`, where an
71
71
  omitted `retryAfterMs` equals `queueTimeoutMs`. The pool belongs to the executor
72
72
  instance, so a Worker that builds one per isolate bounds each isolate, not the
@@ -90,8 +90,10 @@ also need the lifecycle wrapper above; structural compatibility alone is insuffi
90
90
  ```ts
91
91
  interface Executor {
92
92
  readonly name?: string; // what /health and doctor report
93
+ readonly deadlineMs?: number; // the run's wall-clock deadline (`L3`)
93
94
  execute(code: string, providers: ExecutorProvider[]): Promise<ExecuteResult>;
94
95
  close?(): void | Promise<void>;
96
+ readonly maxReadBytes?: number; // whole-read ceiling, default 1.5 MiB (`S11`)
95
97
  }
96
98
 
97
99
  interface ExecutorProvider {
@@ -115,8 +117,8 @@ Connecta passes exactly one provider, named `connecta`. An executor must:
115
117
  called with the program's arguments and awaited. Connecta supplies `search`,
116
118
  `describe`, `call`, `read`, `result`, `skill`, and `emit`; the prelude also
117
119
  binds the guest's `skills` alias to `skill`. Connecta also supplies
118
- `unawaited`, which the built-in prelude calls at most once, after a failed
119
- program read a result field off an unawaited call. It spends no host call;
120
+ `unawaited`, which the built-in prelude calls at most once, after a
121
+ program read a result field off an unawaited call, whether it then failed or finished. It spends no host call;
120
122
  the host admits one notice per run and ends the run with `budget_exceeded`
121
123
  on any further call, so a bridge that exposes it directly gains no
122
124
  uncounted round trips.
@@ -142,7 +144,11 @@ Connecta passes exactly one provider, named `connecta`. An executor must:
142
144
  call order (`R5`), bounding what it retains.
143
145
  6. **Bound the guest**: wall clock, memory, stack, and CPU (`L3`, `L5`), within
144
146
  `P2`/`X5`. Settle `execute()` on your own deadline; connecta stops waiting
145
- at `execute.watchdogMs` and reports the sandbox unresponsive (`L3`).
147
+ at `execute.watchdogMs` and reports the sandbox unresponsive (`L3`). Expose
148
+ that deadline as `deadlineMs`, a positive whole number of milliseconds, below
149
+ `execute.watchdogMs`, so connecta can end reads before it and state it in the
150
+ tool description. Without it, reads end only just before `execute.watchdogMs`,
151
+ so a slow one can leave a slower program no time to finish.
146
152
  7. **Grant no ambient authority.** Never use `eval` or `node:vm`: sandboxing
147
153
  supplements connecta's boundary; every capability arrives through `fns`.
148
154
 
@@ -170,7 +176,9 @@ wrap a bare body when driven directly; that is outside the portable contract.
170
176
  exposes is outside the portable contract and must not be used. QuickJS grants
171
177
  none of it. A loader-only Dynamic Worker denies external egress and filesystem
172
178
  access and keeps its environment maps empty, but exposes the globals and runtime
173
- builtins in `X5`.
179
+ builtins in `X5`. Denied egress does not stop a read tool's own arguments from
180
+ carrying data. [`execute_read_code`](./meta-tools.md#read-programs) runs under this
181
+ same contract and refuses calls that can write.
174
182
 
175
183
  **P3.** Values cross the host bridge as JSON: arguments must be
176
184
  JSON-serializable and results arrive as plain JSON. A value outside JSON — a
@@ -248,7 +256,9 @@ unchanged as `connecta.call(entry.address, entry.args)`
248
256
  ([fully determined operations](./meta-tools.md#discovery-context) only).
249
257
 
250
258
  Each entry carries `address`, `name`, and — when requested — `description`,
251
- the rendered schemas, and `annotations`. The page's `connectors` map holds each
259
+ the rendered schemas, and `annotations`. `description` is the one-sentence
260
+ [summary](./meta-tools.md#lexical-discovery), with `descriptionTruncated: true`
261
+ when text was left out; `fullDescriptions: true` returns the original text. The page's `connectors` map holds each
252
262
  row's connector facts once, keyed by the id before the address's first dot: the
253
263
  configured `title` when present (normalized whitespace, at most 120 UTF-8
254
264
  bytes), and `guide` (the skill name) with a bounded `guideSummary` when the
@@ -354,7 +364,14 @@ and catalog failures add only `retryAfterMs` when known. Each failed entry
354
364
  clamps its caller-authored `address` to 512 UTF-8 bytes with an `…` marker, and
355
365
  entry order correlates a clipped address with its request; successes keep
356
366
  canonical addresses. More than 100 addresses is `invalid_args`, and the same
357
- 256,000-byte ceiling applies. Compact describe keeps property prose within a
367
+ 256,000-byte ceiling applies. `description` is the tool's full text. One call
368
+ returns at most 32 KiB of UTF-8 full description text in address order; an entry
369
+ that does not fit in what remains gets the search summary, with
370
+ `descriptionTruncated: true` when that summary leaves text out, and a later,
371
+ shorter entry may still fit. Summaries have their own allowance and spend none of
372
+ that budget: each is at most 160 UTF-16 code units, at most 480 UTF-8 bytes, so
373
+ 100 addresses return at most 80,288 bytes of description text.
374
+ `fullDescriptions: true` lifts the budget, leaving the ceiling. Compact describe keeps property prose within a
358
375
  separate 8,192-byte UTF-8 shape cap and shares search's 2,000-visit budget; a
359
376
  capped shape sets `inputSchemaTruncated` or `outputSchemaTruncated`, and
360
377
  `format: "json"` gives the exact schema. `format: "typescript"` returns a
@@ -370,9 +387,12 @@ The `execute_code` description declares a compact subset of it inline: argument
370
387
  shapes that match what `GuestApi` accepts, result fields it returns, and no
371
388
  others. Authored emit blocks are the exact variants `M1` accepts;
372
389
  forwarded blocks are the native variants from `result.content`. It also
373
- states that search and describe return objects rather than arrays, that a call
374
- result over 1 MiB returns a paged handle, and that every function except `emit`
375
- spends a host call (`M7`). `GuestApi`'s `emit` takes no paged handle, which the
390
+ states that search and describe return objects rather than arrays, that a row with
391
+ `guideRequired` needs its connector guide first (only when a visible connector has
392
+ a guide), that a call result over 1 MiB returns a paged handle to loop with
393
+ `connecta.result`, and that every function except `emit` spends a host call and
394
+ that passing the limit ends the program with nothing returned, even under
395
+ `Promise.allSettled` (`M7`). `GuestApi`'s `emit` takes no paged handle, which the
376
396
  host refuses. Programs still use plain JavaScript.
377
397
 
378
398
  ```js
@@ -414,6 +434,31 @@ it can reach.
414
434
 
415
435
  ### connecta.result and connecta.skill
416
436
 
437
+ **S11.** One rule covers size in a program: a program sees a whole value up to
438
+ the read ceiling; what it returns is capped at `maxResultChars` (`R2`); anything
439
+ larger carries a `resultId`.
440
+
441
+ `await connecta.result(handleOrId, { all: true })` returns the whole stored value
442
+ as `{ data, format }`, for one host call however many transfer pages it takes
443
+ (`L6`). A handle's `valueFormat` decides the format. JSON that won't parse, as when
444
+ redaction replaced a number, throws and says to page instead.
445
+ A bare id parses as JSON when it can, and is text otherwise, so pass the handle when
446
+ the difference matters.
447
+ `all` takes no other option; `{ all: false }` is ordinary paging. Each transfer
448
+ page takes the same bindings, rechecks and deadline as an explicit page: a result
449
+ its run did not publish (`R4a`) fails with its usual code, and a slow page is a
450
+ catchable `timeout`.
451
+
452
+ The read ceiling is 1.5 MiB of the stored UTF-8. `quickJsExecutor()` scales it by
453
+ `cpuTimeMs / 250`, at most 2 MiB and at most a sixteenth of `memoryLimitBytes`:
454
+ parsing a value and using it costs CPU and heap in proportion to its size. The Dynamic Worker
455
+ uses 1.5 MiB; Miniflare read 16 MB without failing, but that says nothing about
456
+ production isolate limits. A custom executor may set `maxReadBytes` lower. Over it the call throws
457
+ `result_too_large`, catchable, naming the size and the ceiling, after one host call
458
+ and no transfer. Page with `{ offset }` and keep only what you need from each page.
459
+
460
+ Reading whole leaves the stash in place: read it again, page it, or return the id.
461
+
417
462
  `await connecta.result(handleOrId, { page?, offset?, maxBytes? })` reads a stashed direct-call
418
463
  or program-call result bound to the admitted subject, principal, endpoint/pool, request origin,
419
464
  connector, and tool. It returns
@@ -437,8 +482,9 @@ connector id as top-level `skills`, within the admitted connector view; built-in
437
482
  names stay reserved (see
438
483
  [meta-tools](./meta-tools.md#connector-guides-and-skills)). It reads the
439
484
  registry behind native `skills/get` and `resources/read`, including opted-in
440
- downstream skills. It returns
441
- `{ name, format: "text", text }`; an unknown name is `not_found`. It also
485
+ downstream skills. It returns `{ name, format: "text", text }`, where a local
486
+ guide's `text` omits the frontmatter native readers receive; an unknown name is
487
+ `not_found`. It also
442
488
  accepts the top-level tool's `{ name }` object, and `connecta.skills` is an
443
489
  undeclared alias of `connecta.skill` with the same host-call accounting, because
444
490
  models copy the `skills` tool's shape into programs. Any other argument is
@@ -562,7 +608,8 @@ access (`L4`); Workers guest computation may continue briefly until teardown (`X
562
608
 
563
609
  Both executor bridges rebuild typed host rejections as guest Errors and retain
564
610
  their identity in executor-owned state (`X11`). `message` stays human text, capped at 2,000
565
- JSON-serialized characters including quotes and an `…` marker when clipped.
611
+ JSON-serialized characters including quotes and a `… [truncated]` marker when clipped,
612
+ and cut only between tokens.
566
613
  `code` and `retryable` are the stable branch fields; `data` and `details` carry the same host
567
614
  classification, which fits 3,700 serialized characters. Optional recovery metadata
568
615
  that would exceed that bound is omitted whole, preserving `code`, `message`,
@@ -610,20 +657,22 @@ direct calls; and `input_required_invalid`, `input_required_limit`,
610
657
  | `input_required_unsupported` | a downstream asked for input; a read gets `nextAction` for the equivalent direct call, a write is an unknown outcome (`writeOutcome: "unknown"`) to reconcile before any fresh call | false |
611
658
  | `rate_limited` | the downstream reported a rate limit | true |
612
659
  | `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 |
613
- | `timeout` | a call or sandbox deadline expired; details name operation, stage, elapsedMs, and deadlineMs | per operation |
660
+ | `timeout` | a call or sandbox deadline expired; details name operation, stage, elapsedMs, and deadlineMs; at `write gate` or `dispatch gate`, it was not sent (`L3`) | per operation |
614
661
  | `program_error` | guest JavaScript failed; `details.name`, `line`, and a fixed repair hint describe it | false |
615
662
  | `cancelled` | the run ended while this call was in flight (`E5`) | false |
616
663
  | `connector_call_failed` | anything else the connector threw | per message |
617
664
  | `catalog_lookup_failed` | the connector's catalog could not be loaded | per cause |
618
665
  | `result_processing_failed` | the result could not be prepared | per message |
619
- | `result_too_large` | a discovery response exceeded its byte bound | false |
666
+ | `result_too_large` | a discovery response or a whole result read exceeded its byte bound | false |
667
+ | `result_not_retained` | paging a result its run did not publish (`R4a`): rerun a read, never repeat a write | false |
620
668
  | `budget_exceeded` | the run exhausted a host-call, write, or emitted-output budget | false |
621
- | `write_outcome_unknown` | a program write was sent and no answer came back; it is never sent again (`W9`) | false |
669
+ | `write_outcome_unknown` | a program write was sent and no answer proves whether it applied; `unknownWrites` says how each failed, and none is sent again (`W9`) | false |
622
670
  | `executor_failed` | the executor returned a malformed result or failed outside the guest program | false |
623
671
 
624
672
  **E3.** `auth_required` carries the same recovery envelope as `call_tool`:
625
673
  `connector`, `operation`, `recovery` (`oauth`, `operator_config`, or
626
- `unavailable`), `nextAction` naming `authorize_connector`, and a `retry`
674
+ `unavailable`), a `nextAction` carrying the handoff as
675
+ [authorization recovery](./meta-tools.md#authorization-recovery) describes, and a `retry`
627
676
  sentence. A program cannot recover credentials — only an operator can — so stop
628
677
  and let the failure reach the model.
629
678
 
@@ -660,11 +709,16 @@ non-enumerable getters for result fields (`data`, `format`, `content`, `tools`,
660
709
  `catalogErrors`, `total`, `text`, `resultId`, `hasMore`, `nextOffset`). Reading
661
710
  one before `await`, as in `connecta.call(...).data` or
662
711
  `const { tools } = connecta.search(...)`, returns undefined as before and sets
663
- a per-run flag. When that program then fails, the prelude reports the flag
664
- through the uncounted `unawaited` host function before the failure crosses,
665
- and the hint is `connecta.* returns a Promise; await it`. Awaiting, `then`,
666
- `Promise.all`, and `Promise.allSettled` never read those fields. Hints are
667
- fixed text; no program source, field name, or value enters them.
712
+ a per-run flag. The prelude reports the flag through the uncounted `unawaited`
713
+ host function before the program's outcome crosses. A failed program's hint is
714
+ `connecta.* returns a Promise; await it`; a program that still finished carries
715
+ the same words in `note` (`R1`). Awaiting, `then`, `Promise.all`, and
716
+ `Promise.allSettled` never read those fields. A `TypeError` saying a value is not a
717
+ function or not iterable, in a program that uses `connecta.search`, `describe` or
718
+ `call`, names the result shape: `search and describe return { tools }; call returns
719
+ { data, format }.` A syntax error in source that does not start
720
+ as a program says `Pass one expression: async () => { ...; return answer; }.`
721
+ Hints are fixed text; no program source, field name, or value enters them.
668
722
 
669
723
  An unchanged uncaught host error retains its classification through Error
670
724
  identity. A new error containing its message is a new program error. Copied
@@ -715,6 +769,15 @@ fields. Program-authored projection saved bytes; host heuristics could drop
715
769
  deliberately returned fields. Helpers need [#222](https://github.com/zackbart/connecta/issues/222)
716
770
  evidence that programs fail to project.
717
771
 
772
+ A program that finished with nothing to read looks like nothing happened, and a
773
+ write inside it must not run again. When `result` is absent and nothing was
774
+ emitted, the response carries a fixed `note`. After a write:
775
+ `Program returned nothing. Its writes were already sent. Do not rerun it, even with a fix: read their targets in a new program.`
776
+ After reads only: `Program returned nothing, though its calls ran. Add a return.`
777
+ With no calls: `Program returned nothing and made no host calls. Add a return.`
778
+ A returned `null` is a return. An unawaited read (`E6`) puts its await hint in
779
+ `note` even beside a value, followed by the write sentences when a write was sent.
780
+
718
781
  **R2.** The boundary is `execute.maxResultChars` serialized characters, shared
719
782
  with emitted text: 24,000 (~6k tokens) by default, configurable from 4,000 to
720
783
  1,000,000. Raise it when agents need large results whole and the model's
@@ -727,14 +790,25 @@ with no content (see [truncated direct-call results](./meta-tools.md#truncated-d
727
790
  so keep the cap below what every client of the deployment accepts. Redaction scans the whole envelope once, so its cost grows with the cap.
728
791
  The cap does not change stash paging: pages still obey the stashed call's
729
792
  `maxResultBytes` and the 32 KiB bridge ceiling.
793
+ A direct read over its cap may answer with a [preview](./meta-tools.md#previews-of-reads-over-the-cap)
794
+ whose `nextAction` is a program: it loops `connecta.result` over the retained
795
+ result, parses it, and ends in a line the agent edits to reduce it. Editing that
796
+ line and running it costs one host call per page, and a result it could not walk
797
+ within `maxHostCalls` gets no preview. A program never sees a preview: its calls
798
+ and its pages return the whole value. A read at or under its cap returns whole.
799
+ If the host refuses the program, the rest of that result can't be read, and the
800
+ notice says to narrow the read or say what was not read.
730
801
  An oversized return is stashed as compact JSON through the same
731
802
  result store as direct calls. The returned notice carries `truncated`, `resultId`,
732
- `totalBytes`, `hint`, `nextOffset: 0`, and an executable `nextAction` that pages
733
- with `connecta.result`. Its page size is `floor((maxResultChars - 1024) / 6)`
734
- bytes, so a page fits the next program's return even when every byte escapes to
735
- six characters (3,829 bytes by default), at most the stashed call's
736
- `maxResultBytes` and the 32 KiB bridge ceiling; direct-call notices use the same
737
- size. It also retains `totalChars` and a bounded `preview`.
803
+ `totalBytes`, `hint`, `nextOffset: 0`, and an executable `nextAction`. Within the
804
+ read ceiling (`S11`), that program reads the return whole with
805
+ `connecta.result(id, { all: true })` and marks where to reduce it; as written it
806
+ returns a prefix that fits, so running it unedited cannot overflow again. Past the
807
+ ceiling it pages. Both use a size of `floor((maxResultChars - 1024) / 6)` bytes, so
808
+ the output fits the next program's return even when every byte escapes to six
809
+ characters (3,829 bytes by default), at most the stashed call's `maxResultBytes`
810
+ and the 32 KiB bridge ceiling; direct-call notices page at the same size. It also
811
+ retains `totalChars` and a bounded `preview`.
738
812
  The recovery action and hint precede the preview, so clients that spill results
739
813
  after the first 2,000 characters retain the handle and the instruction.
740
814
  Small returns pass through unchanged; Connecta does not select fields.
@@ -768,6 +842,47 @@ its dependencies forward to any new return stash. The page cap is the smallest
768
842
  of the deployment cap and contributing connectors' effective `maxResultBytes`.
769
843
  Read-only endpoints cannot page write results. A UUID is a handle, never a grant.
770
844
 
845
+ **R4a.** A run's retained results are unreadable from other requests by default.
846
+ One becomes readable only after its bytes pass the run's final secret set, including
847
+ any credential or private argument registered after they were stored. This covers
848
+ every result the run stashes: oversized `connecta.call` reads and writes, those
849
+ transferred without showing a handle, and an oversized return. An id the program
850
+ places in its return value, its error or a later call's arguments is covered alike.
851
+ Executors do not take part, so QuickJS, Worker and custom executors behave the same.
852
+
853
+ Each entry's first chunk, which a reader needs, stays in memory until the run ends,
854
+ and paging inside the run reads it from there. Every way a run answers then seals.
855
+ Sealing starts no further host calls, then waits for the run's host calls and the
856
+ request's credential reads to finish, so its secret set is final. Only then does it
857
+ write first chunks. An entry the set has not grown past is written with no other
858
+ storage work. After growth, its stored bytes are read back and checked, in stored
859
+ form and structurally, so JSON-escaped, URL-encoded and base64 forms and a form split
860
+ across text blocks are found. An entry with no match is written unchanged. An entry
861
+ whose JSON was serialized from a typed array, `Buffer`, `DataView` or `ArrayBuffer`,
862
+ including one a `toJSON` holds or returns, is not read back and stays unreadable
863
+ after growth, since those bytes are checked only while still binary. Stored bytes are
864
+ never rewritten, so
865
+ `totalBytes` and any offset issued during the run stay valid in later runs.
866
+
867
+ An entry that was not written stays unreadable: it matched the final set, a read-back
868
+ or write failed, or work was still pending at the response's answer-by time, which
869
+ is `execute.watchdogMs` plus a margin of at most 2 seconds. None of these publishes
870
+ anything first and withdraws it later. The run's response carries a `note` that
871
+ paging such an id may fail and to rerun the read, and with a write's output among
872
+ them, not to repeat that write. Paging it from a later request fails with
873
+ `result_not_retained`, `retryable: false`: a read's message says to rerun, and a
874
+ write's says its output is not retrievable and not to repeat it. That answer comes
875
+ from a marker holding the entry's binding and no bytes. If storing the marker fails,
876
+ the id answers `not_found`. Cancellation starts no marker or cleanup; storage expiry
877
+ reclaims the chunks. A write's output is always retrievable inside the run that wrote
878
+ it, so a program that needs it can read it before it ends.
879
+
880
+ A direct `call_tool`, `call_destructive_tool` or native tool's stash has no later
881
+ call in its request to wait for, so its bytes pass that request's set when stored,
882
+ and no seal applies. A direct call in a different request shares no secret set
883
+ with it: a credential first used there never checks an earlier direct entry, just
884
+ as it never checks that earlier call's inline output.
885
+
771
886
  If paging is unavailable because the stash is full, storage fails, or text
772
887
  cannot round-trip as UTF-8, the successful return keeps the existing bounded
773
888
  `{ truncated, preview, totalChars, hint }` fallback. Its hint explains that
@@ -943,7 +1058,9 @@ use `execute.hostCallTimeoutMs`. The `execute_code` description advertises the
943
1058
  `connecta.call` default as `<n>s/host call`, or, when `calls.defaultTimeoutMs`
944
1059
  differs, both deadlines, for example `25s/call (15s result/skill/read)`.
945
1060
  A guest may request a longer individual call, but the sandbox
946
- and request ceilings still bound the whole run. Timeout details are agent-facing;
1061
+ and request ceilings still bound the whole run (`L3`). A direct call's deadline, however
1062
+ it was set, ends at the response ceiling, `execute.watchdogMs` from the
1063
+ request's arrival, and states the deadline it ran under (`L3`). Timeout details are agent-facing;
947
1064
  operator records retain only checked classifications and numeric facts.
948
1065
  Result processing, including stash reservation, uses the same remaining call
949
1066
  budget and request cancellation. If it cannot finish after a successful call,
@@ -961,15 +1078,95 @@ appear in `timeouts`, bounded to ten entries.
961
1078
 
962
1079
  **L3.** Every execution runs under a wall-clock deadline that includes time
963
1080
  spent waiting on host calls. Expiry ends the run with an execution error and no
964
- partial result; the deadline's length is executor configuration (`X1`). Above
965
- it sits a ceiling connecta enforces outside the sandbox, `execute.watchdogMs`
966
- (120 s by default, above both executors' own deadlines), because an executor's
967
- deadline may live inside the sandbox it is meant to stop: a wedged Dynamic
968
- Worker never fires its in-isolate timer. A run whose executor has not settled
969
- by the ceiling ends as a non-retryable `timeout` whose message calls
970
- the sandbox unresponsive, with no partial result, and its admission lease is
971
- released, so wedged runs cannot fill the code pool. Raising an executor's
972
- deadline past the ceiling means raising the ceiling too.
1081
+ partial result; the deadline's length is executor configuration (`X1`), which
1082
+ an executor publishes as `deadlineMs` (QuickJS 30 s, Dynamic Worker 50 s by
1083
+ default).
1084
+
1085
+ Reads stop before that deadline so one slow read costs only itself. Each
1086
+ `connecta.call` read, `read`, `result` and `skill` runs under the smaller of its
1087
+ own deadline (`L4`) and the time left until the run deadline minus a margin of
1088
+ 1 s or a tenth of the deadline, whichever is less, and never past the same margin
1089
+ before the response ceiling below; scoped `search` and `describe`
1090
+ are bounded the same way. Those clocks start before the executor's, so they end
1091
+ early rather than late. A read that reaches its cap fails with a retryable
1092
+ `timeout` the program can catch, so `Promise.allSettled` keeps every result that
1093
+ finished. Caught or not, it is the read's failure, not a sandbox timeout. A
1094
+ later read gets the one-tick deadline that is left, and fails the same way.
1095
+ The cap ends the work as well as the wait: the read's downstream signal aborts
1096
+ before the program hears `timeout`, and a listing other callers still wait on is
1097
+ cancelled once the last of them leaves. Storage operations cannot be withdrawn
1098
+ once sent, so one in flight runs to its end. A discovery-health routine already
1099
+ under way cannot be interrupted either, and it may start its next operation,
1100
+ such as reading the connector's generation key, after the program hears
1101
+ `timeout`. No deadline bounds that storage latency.
1102
+
1103
+ Writes are exempt, because a write ended early is a write with an unknown
1104
+ outcome. A write-like call (`W9`), a source write overridden to read included,
1105
+ keeps its own deadline up to `execute.watchdogMs`, and when it outlasts the run
1106
+ the drain still learns its outcome. Classification comes from the catalog, so
1107
+ resolution uses the read's cap for every call: nothing has been sent if it
1108
+ expires. A write that reaches its gate after reads have stopped is refused
1109
+ unsent with a retryable `timeout` at `details.stage: "write gate"`: its answer
1110
+ could not reach the program, so a known "not sent" is worth more than a write
1111
+ only the final counts could report. It spends no write budget and adds nothing
1112
+ to `writes`. Send it again in a new program. If an earlier write in the run landed,
1113
+ a program that lets this refusal end it is not retryable as a whole (below): send
1114
+ only what is left.
1115
+
1116
+ The same time is checked once more as the last step before a call reaches its
1117
+ connector, for reads and writes alike, because resolution, admission or auth can
1118
+ spend it after the write gate, and synchronous work can cross it before a timer
1119
+ runs. A call refused there is a retryable `timeout` at
1120
+ `details.stage: "dispatch gate"` and was not sent. A write refused there had
1121
+ already passed its gate, so it spends write budget and counts in `writes.failed`.
1122
+
1123
+ Above the run deadline sits the response ceiling, `execute.watchdogMs` (55 s by
1124
+ default, above both executors' own deadlines). Connecta enforces it outside the
1125
+ sandbox, because an executor's deadline may live inside the sandbox it is meant
1126
+ to stop: a wedged Dynamic Worker never fires its in-isolate timer. The ceiling
1127
+ counts from the inbound request's arrival, so upload and time in the request and
1128
+ code queues spend it, and it also caps every direct call. On Node, arrival is when
1129
+ `listen` received the request's headers, before it buffered the body. On Workers it
1130
+ is the route's start, which reads the body afterwards. A queue wait still running at
1131
+ the ceiling is refused as a retryable `executor_overloaded`. A run with no time left
1132
+ when it would queue or start is refused as a retryable `timeout` at
1133
+ `details.stage: "response ceiling"`: it did not run and sent nothing.
1134
+
1135
+ A run whose executor has passed its own deadline by the ceiling, or published none and never queued, ends as a
1136
+ non-retryable `timeout` whose message calls the sandbox unresponsive, and its
1137
+ admission lease is released, so wedged runs cannot fill the code pool. A run that
1138
+ reaches the ceiling first, because it queued or because its executor's deadline
1139
+ is set past the ceiling, ends as a retryable `timeout` at
1140
+ `details.stage: "response ceiling"`. Neither has a partial result. Each still
1141
+ carries `hostCalls`, and `writes` when the program sent any. A write still on the
1142
+ wire reaches its deadline at the ceiling and makes the result
1143
+ `write_outcome_unknown`, once, with no replay (`W9`).
1144
+
1145
+ `retryable` on a program's final error answers whether the whole program can run
1146
+ again. Once a write in the run has landed, it is `false` however the run ended, a
1147
+ retryable read, the write gate and the ceiling included, and `retryAfterMs` is
1148
+ dropped, because running the program again would repeat that write. Writes that
1149
+ failed, which are proven not applied, leave the failure's own verdict standing.
1150
+
1151
+ Work after the run that can hold its response, such as storing an oversized return
1152
+ for paging, stops at most 2 s past the ceiling, or a tenth of it if that is smaller
1153
+ (57 s by default). Storage still stalled then costs the paging: the program's return
1154
+ comes back as its preview with no `resultId`.
1155
+
1156
+ The ceiling exists because MCP clients abandon a request at about 60 s, and a client's
1157
+ timeout tells the agent nothing: no typed error, no counts, no write outcome.
1158
+ Connecta answers each `tools/call` as one JSON response, so nothing reaches the
1159
+ client until the run ends, and the client's per-request timer runs for all of it. Claude
1160
+ Code's HTTP transport times each request at 60 s unless `MCP_TOOL_TIMEOUT` or a
1161
+ server's `timeout` is higher; Codex's `tool_timeout_sec` defaults to 60 s, and
1162
+ the MCP TypeScript SDK's request timeout defaults to 60 s. The default leaves 5 s of that
1163
+ for storage after the run and to reach the client. Raise it only when every client
1164
+ of the deployment allows longer. Then set the client's limit first, keep
1165
+ `execute.watchdogMs` a margin below it, and raise any executor deadline
1166
+ (`quickJsExecutor({ timeoutMs })`, `workerExecutor({ timeout })`) to stay below
1167
+ `execute.watchdogMs`. Construction warns when an executor's deadline does not.
1168
+ Cloudflare sets no wall-clock limit on an HTTP Worker while its client stays
1169
+ connected, so neither figure is a platform limit.
973
1170
 
974
1171
  **L4.** Per-execution bounds that are contract, identical in both executors
975
1172
  because connecta enforces them above the sandbox:
@@ -995,9 +1192,9 @@ current page, then pass that cursor to the tool in the next program.
995
1192
 
996
1193
  Every `call`, `read`, `search`, `describe`, `result`, and `skill` spends one host
997
1194
  call on entry, before resolution, validation, or dispatch; automatic transfer
998
- pages of a paged result do not. Catching a local refusal refunds nothing.
1195
+ pages of a paged result and of `result(id, { all: true })` do not. Catching a local refusal refunds nothing.
999
1196
  The first call beyond the budget ends the host run with one non-retryable
1000
- `budget_exceeded` (`E2`). Its bridge stays pending until lease disposal so guest
1197
+ `budget_exceeded` (`E2`), whose message says the run ended with nothing returned, to split the remaining work across programs within the limit, and not to resend any write already sent. Its bridge stays pending until lease disposal so guest
1001
1198
  `try/catch` cannot swallow it. Later host access, including `emit`, stops; pending
1002
1199
  replies are withheld. The refusal reaches no connector, returns no partial result,
1003
1200
  and discards accepted emits (`M4`). Calls still resolving or awaiting admission
@@ -1032,10 +1229,14 @@ bridge. The trusted guest initializer reconstructs values up to 1 MiB from
1032
1229
  bounded pages, retaining JSON versus text format and every payload code point,
1033
1230
  including U+FEFF at page starts. Larger values return a
1034
1231
  `{ format: "paged", valueFormat, resultId, totalBytes, truncated: true, hint }`
1035
- handle. Pass the handle or its `resultId` to `connecta.result` and reduce the
1036
- pages before returning them. Automatic transfers spend no additional host-call
1037
- budget; explicit paging does. This policy is shared by QuickJS, Worker and custom
1038
- executors that run provider preludes.
1232
+ handle. Within the read ceiling its `hint` says to read it with
1233
+ `connecta.result(handle, { all: true })` (`S11`). Past the ceiling, pass the handle or
1234
+ its `resultId` to `connecta.result` and reduce the pages before returning them.
1235
+ Automatic transfers spend no additional host-call budget; explicit paging does, so
1236
+ such a read's `hint` offers the whole loop only when it fits the calls left and
1237
+ otherwise bounds the pages per program
1238
+ ([paging](./meta-tools.md#paging-with-connectaresult)). This policy is shared by
1239
+ QuickJS, Worker and custom executors that run provider preludes.
1039
1240
  Oversized raw text containing unpaired UTF-16 surrogates returns a handle without
1040
1241
  `resultId`, with a notice that it cannot be paged as text. This preserves the
1041
1242
  UTF-8 byte-offset contract without silently replacing code units. The completed
@@ -1044,9 +1245,18 @@ round-trip losslessly.
1044
1245
 
1045
1246
  Stashes retain their existing bindings, 15-minute expiry, per-connector page
1046
1247
  caps and aggregate capacity charges on memory, SQLite and D1. Capacity exhaustion
1047
- or a storage failure returns a successful notice without a `resultId`. A failed
1248
+ or a storage failure returns a successful notice without a `resultId`. A read's
1249
+ value reconstructed through transfer pages gives its stash back once it is whole: the
1250
+ guest never saw a handle for it, so nothing can name those bytes. Giving back removes
1251
+ the entry from the run's memory ledger first, so sealing (`R4a`) has nothing to publish
1252
+ for it; once sealing has begun the entry stays until it expires. A run gives back at
1253
+ most three stashes within a shared one-second budget, and the rest of its entries stay
1254
+ until they expire, so neither slow storage nor a long run of reads costs the answer. A write's stash, and a
1255
+ handle the guest received, keep their charge until they expire, including after
1256
+ `{ all: true }`. A failed
1048
1257
  automatic reconstruction retains the handle. Neither result path reissues the
1049
1258
  call or changes a completed write into a failed or unknown write. Guest returns use the same store when they exceed the presentation cap (`R2`–`R4`).
1259
+ Pages from a later run see these results only after the run publishes them (`R4a`).
1050
1260
 
1051
1261
  Over-bound arguments and other unpaged bridge results still fail catchably with
1052
1262
  executor-owned text. An over-bound result names the called address; an argument
@@ -1073,10 +1283,11 @@ on arrival. A queued run also holds its MCP request permit
1073
1283
  ([request admission](./architecture.md#admission-in-two-places)) while it waits.
1074
1284
 
1075
1285
  **L8.** Result, log, host-call, and emission budgets are deployment configuration.
1076
- A call may request `timeoutMs` just as a direct call does; the guest deadline is
1077
- capped by the time remaining before the program watchdog. `execute_code`'s description states the host-call
1078
- budget and the per-call deadline — the ones a program must plan around before it
1079
- runs. The result and log caps live here and in the truncation notice itself
1286
+ A call may request `timeoutMs` just as a direct call does. A read's deadline is
1287
+ capped by the time left before reads stop, a write's by the time left before the
1288
+ program watchdog (`L3`). `execute_code`'s description states the host-call
1289
+ budget, the per-call deadline and, when the executor publishes one, the run
1290
+ deadline as `<n>s/run` — the ones a program must plan around before it runs. The result and log caps live here and in the truncation notice itself
1080
1291
  (`R2`, `R5`).
1081
1292
 
1082
1293
  ## Activity
@@ -1113,9 +1324,18 @@ fields that are otherwise operator- and connector-authored, so `connectorId`
1113
1324
  and `toolName` clamp at 128 UTF-8 bytes (`address` at 257) with a `…` marker:
1114
1325
  payload-free _by construction_ means the event has nowhere to put a payload.
1115
1326
 
1116
- **V4.** The execution itself emits no event. It has no address, and its one
1117
- distinctive output is the program source — exactly what a payload-free history
1118
- must never keep.
1327
+ **V4.** By default the execution and its utility operations emit no events.
1328
+ With `activityHistory({ operations: true })`, each executed search, describe,
1329
+ result page and skill read adds a `kind: "operation"` row, and the whole program
1330
+ adds a `program` row with host-call counts. Utility attempts refused above the
1331
+ execution boundary remain represented by the program's host-call accounting.
1332
+ Rows use fixed operation names rather than argument-supplied addresses or IDs;
1333
+ they carry timings, typed outcomes/codes and serialized return byte counts,
1334
+ never program source or payloads. The existing detached sink handles storage,
1335
+ and these rows do not count as calls or friction. The
1336
+ [operation-history contract](./operator-ui.md#opt-in-to-operation-history)
1337
+ owns opt-in cost, scope and owner disclosure rules. Recording history changes
1338
+ no authorization or capability state.
1119
1339
 
1120
1340
  **V5.** A write a program issues after it has already returned is not
1121
1341
  sent and records nothing: it was never an attempt (`W9`).
@@ -1146,7 +1366,8 @@ createConnecta({
1146
1366
  reporting: { tools: ["posthog"], readOnly: true, members: analysts },
1147
1367
  },
1148
1368
  classification: {
1149
- posthog: { exec: "read" }, // only for a deployment with a read-only exec contract
1369
+ posthog: { exec: "read" }, // only if the server is silent about exec and exec is safe to repeat
1370
+ crm: { save_view: "read-repeatable" }, // declared a write; checked, and a repeat is accepted
1150
1371
  vendor: { mislabeled_update: "write" },
1151
1372
  },
1152
1373
  });
@@ -1163,7 +1384,7 @@ all calls this deployment can make through it.
1163
1384
 
1164
1385
  | Precedence | Evidence | Result |
1165
1386
  | ---------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
1166
- | 1 | Exact deployment `classification[connectorId][toolName]` | `read`, `write`, or `hidden`, even over a stale provider review |
1387
+ | 1 | Exact deployment `classification[connectorId][toolName]` | `read` where the source is silent, `read-repeatable`, `write`, or `hidden`; only `read-repeatable` can serve a source's declared write as a read |
1167
1388
  | 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 |
1168
1389
  | 3 | Downstream annotations | Read only when `readOnlyHint === true && destructiveHint !== true`; otherwise write |
1169
1390
 
@@ -1172,8 +1393,14 @@ fail construction. An absent runtime-discovered tool leaves its override
1172
1393
  dormant and emits an operator warning once per catalog change; the override applies if the name returns.
1173
1394
  Hidden tools are absent from search and describe and refuse every call, including
1174
1395
  a guessed name. `"write"` selects the host approval path. MCP recovery sends a
1175
- tool only once when either its effective or source class is write, even when an
1176
- override changes write to read. The source class is the verified provider review
1396
+ tool only once when either its effective or source class is write, including a
1397
+ tool the server is silent about and an override sets to read. A `read` override on a
1398
+ tool whose source declares a write (`readOnlyHint: false`, `destructiveHint: true`,
1399
+ or contradictory hints) has no effect beyond an operator warning: a host may resend
1400
+ a read-annotated call, so that tool stays a write. Where the source is silent, the
1401
+ override asserts that the call is safe to repeat. `read-repeatable` serves a declared
1402
+ write as a read in programs and everywhere else, and keeps these single-send rules; the
1403
+ deployment accepts that a host may repeat the call. The source class is the verified provider review
1177
1404
  where present during migration, otherwise the server annotations. It also governs
1178
1405
  authorization recovery, program re-runs and ambiguous failure outcomes: after
1179
1406
  dispatch, an unknown outcome has no retry or wait advice, including on timeout or
@@ -1184,8 +1411,49 @@ They are never inferred from descriptions or arguments.
1184
1411
  if the program does not await it, bounded by its host-call deadline. A write
1185
1412
  that reaches accounting after the program settles is not sent. An unanswered
1186
1413
  write makes the run fail `write_outcome_unknown` with
1187
- `writes: { succeeded, failed, unknown }`, regardless of its return value.
1188
- A failed program that already wrote carries those counts on its error. No
1414
+ `writes: { succeeded, failed, unknown }`, regardless of its return value. Its
1415
+ `unknownWrites` lists at most ten such writes, each with `address` when resolved,
1416
+ `code`, the redacted `message`, and `toolAnswered: true` when the downstream tool
1417
+ itself answered `isError`. `unknownWritesTruncated` marks a write left out or a
1418
+ message dropped. When a tool answered, the error's `message` leads with
1419
+ `<address> answered: <the tool's words>`, and that entry carries no second copy.
1420
+ An authorization verdict keeps its own lead, with the words in its entry. The
1421
+ words say why the tool answered as it did, not that nothing applied, so read them
1422
+ and check the target before acting. Give the user any approval link or request id
1423
+ they hold. Never rerun the program: writes it already sent would repeat. If the
1424
+ tool's words call for a further call, such as one carrying a provider's approval
1425
+ token, send only that operation, as a new call. Connecta does not block it.
1426
+
1427
+ A tool's words reach any agent up to 4,096 UTF-8 bytes, on direct and native calls
1428
+ too. A longer text is cut between tokens and ends `… [truncated]`, so a link
1429
+ arrives whole or not at all. A tighter result cap on a direct call cuts it further.
1430
+ A program's report is redacted against the run's final credentials before any cut,
1431
+ holds at most 4,096 serialized bytes per message and 8,192 in all, and stays inside
1432
+ `execute.maxResultChars`. Activity and logs never receive these words.
1433
+
1434
+ Program arguments pass through the credentials this request has already sent. The
1435
+ check matches a whole credential or eight consecutive characters of what follows
1436
+ its public framing: an auth scheme, a vendor prefix such as `sk_test_`, or a JWT's
1437
+ header segment. Naming a prefix is no match, an identifier sent beside a credential
1438
+ (a confidential client's ID, a Basic username, or a maintained provider's declared
1439
+ header: Cloudflare's `X-Auth-Email`, Overflow's `x-client-id`) is no match, and a
1440
+ private `writeOnly` value is no match. A read that matches is sent with the match replaced. A write that matches
1441
+ is refused before dispatch with `invalid_args`, because it is never sent altered:
1442
+ nothing leaves, and a value that is genuine can go as its own direct call. A
1443
+ program's first call has no such credentials yet, so this can first appear on a
1444
+ later call. Otherwise a refusal needs one of these: a value that shares eight
1445
+ characters past the prefix with a credential, such as a publishable key beside its
1446
+ secret key; a non-secret value configured as a credential, such as a field not
1447
+ named as an identifier, a `remoteMcp()` or `api()` auth header under any name, or a
1448
+ query parameter with a sensitive-looking name; or text the check cannot finish (escaping
1449
+ nested past eight layers, or past its work budget). The check is a backstop: fewer
1450
+ than eight characters past a prefix pass it, and a program never receives what
1451
+ output redaction withheld.
1452
+
1453
+ A failed program that already wrote carries those counts on its error. The
1454
+ write-budget refusal is `execute_code write budget exceeded (10 writes per run); <address> was not sent.`,
1455
+ followed by checking the run's writes count and sending only the remaining writes
1456
+ in a new program. No
1189
1457
  write is automatically retried, including an ambiguous timeout. A downstream
1190
1458
  refusal can establish failure only when it covers the whole call: a request
1191
1459
  connecta never sent, or an HTTP 4xx refusal (not 408) of the only request the
@@ -1215,7 +1483,7 @@ omit write tools from `search_tools`, `connecta.search`, and
1215
1483
  `connecta.describe`, and refuse every write call through `call_tool`,
1216
1484
  `call_destructive_tool`, and programs with `read_only_endpoint` before
1217
1485
  validation or dispatch. Guides and skills are documentation and may still name
1218
- those tools. Meta endpoints list the same six tools in both modes.
1486
+ those tools. Both modes keep `execute_code` and every other meta tool; [`execute_read_code`](./meta-tools.md#read-programs) is listed only on writable human endpoints.
1219
1487
  [Native pools](./meta-tools.md#native-tool-surface) list individual downstream
1220
1488
  tools and omit writes when read-only. They retain `execute_code` by default;
1221
1489
  `executeCode: false` replaces it with `read_result`. Approval belongs to the client.
@@ -1229,9 +1497,9 @@ removed. `W1`–`W8` and `W11` remain retired.
1229
1497
  Documented divergences, with reasons. Everything else must match.
1230
1498
 
1231
1499
  **X1. Deadline default.** QuickJS defaults to 30 s wall clock and terminates the
1232
- child; the Dynamic Worker defaults to 60 s and races the program against an
1500
+ child; the Dynamic Worker defaults to 50 s and races the program against an
1233
1501
  in-isolate timer. Both satisfy `L3`; the numbers are each executor's
1234
- configuration and the error text differs.
1502
+ configuration, published as `deadlineMs`, and the error text differs.
1235
1503
 
1236
1504
  **X2. Memory, stack, and CPU mechanism.** QuickJS exposes explicit heap, stack,
1237
1505
  and guest-CPU limits (`L5`); the Dynamic Worker has no such knobs, so workerd's
@@ -1248,6 +1516,8 @@ promise independently of upstream evaluation. Guest computation may continue
1248
1516
  briefly until platform teardown, but no late return, callback, `finally`, call,
1249
1517
  or emit changes the finished host result. Disposal releases request resources;
1250
1518
  it does not guarantee a synchronous guest-CPU kill.
1519
+ If freeing a finished run's runtime fails, the run keeps its answer, a warning is
1520
+ logged and the child is replaced before it can take another run.
1251
1521
 
1252
1522
  **X4. Log rendering and capture.** QuickJS JSON-stringifies non-string arguments
1253
1523
  and captures `log`, `info`, `warn`, `error`, and `debug`; the Dynamic Worker
@@ -1337,6 +1607,12 @@ receive a fixed description.
1337
1607
 
1338
1608
  ### Verification
1339
1609
 
1610
+ The QuickJS child imports only the guest runtime, IPC helpers and dependency-light
1611
+ error/route constants. `test/purity.node.test.ts` walks its emitted import graph
1612
+ to exclude server orchestration, Effect and the MCP SDK. The shared
1613
+ `InvocationFailure` constructor preserves host error identity across existing
1614
+ exports and typed IPC reconstruction.
1615
+
1340
1616
  The earlier alternatives are in [decision history](https://github.com/zackbart/connecta/blob/main/decisions/0001-ethos-verdict-table.md)
1341
1617
  ([#267](https://github.com/zackbart/connecta/issues/267),
1342
1618
  [#270](https://github.com/zackbart/connecta/issues/270)).
@@ -1350,51 +1626,52 @@ rejection, and branded adapter acceptance across module copies.
1350
1626
  declaration against `GuestApi`, checks arguments in both directions, and rejects
1351
1627
  invented, mistyped, and missing members and authored blocks the host refuses.
1352
1628
 
1353
- | Clauses | Test |
1354
- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1355
- | `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`) |
1356
- | `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/deployment-shapes.node.test.ts` (loader-only Worker construction) |
1357
- | `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
1358
- | `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
1359
- | `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (canonical addressing), `test/server.test.ts` (bounded live connector inventory) |
1360
- | `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) |
1361
- | `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
1362
- | `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) |
1363
- | `S5`, `S6` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`, fail-closed annotations, activity parity) |
1364
- | `S7` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (parallel calls and shared admission) |
1365
- | `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) |
1366
- | `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) |
1367
- | `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) |
1368
- | `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` |
1369
- | `E3`, `E4` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`, destructive reroute), `test/identity-write-journey.node.test.ts` (read-only refusals before validation or dispatch, actual QuickJS) |
1370
- | `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) |
1371
- | `E6`, `X8` | `test/guest-api-contract.test.ts` (unknown and inherited members, retained error identities), `test/quickjs-executor.node.test.ts` |
1372
- | `E7` | `test/guest-api-contract.test.ts` (refusals about a `503`-named connector), `test/errors.test.ts` |
1373
- | `R1`, `R2`, `R3`, `R4` | `test/guest-api-contract.test.ts` and `test/guest-api-contract-quickjs.node.test.ts` (small pass-through, shared return paging and unavailable fallback, no repeated writes, live authority), `test/program-result-cases.ts` (reconstruction, redaction, notice survives a 2,000-character spill) |
1374
- | `M6`, `M9` | verdicts; `R2`'s guard, `M1`'s strict typing, and `M2`'s collect-then-deliver are their enforcement |
1375
- | `R5` | `test/guest-api-contract.test.ts` |
1376
- | `R6`–`R8` | `test/guest-api-contract.test.ts` (normal result keys), `test/execute.test.ts` (opt-in operation aggregates, failure paths, payload exclusion) |
1377
- | `Y1`, `Y2`, `Y3` | `test/guest-api-contract.test.ts` (one attempt per call, retryable flags by code) |
1378
- | `Y4` | `test/meta-tools-call.test.ts`, `test/call-admission.test.ts` (one attempt, retry hints, caller reissue) |
1379
- | `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) |
1380
- | `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) |
1381
- | `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) |
1382
- | `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), `test/executor-requests.test.ts` (queued handoffs and refusal shape across separate workerd requests), `test/request-context.node.test.ts` (queued and QuickJS host-call request context) |
1383
- | `L6`, `X10` | `test/guest-api-contract-quickjs.node.test.ts` and `test/guest-api-contract.test.ts` (whole oversized reads and one-write pageable handles), `test/sqlite-storage.node.test.ts` and `test/storage-sql-d1.test.ts` (SQL stash and native D1 fault probes), `test/quickjs-executor.node.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message) |
1384
- | `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/browser/operator-ui.spec.ts` (identity changes), `test/sql-storage-contract.ts`, run by `test/sqlite-storage.node.test.ts` (historical activity rows still round-trip) |
1385
- | `V5`, `W9` | `test/program-writes.test.ts` (unawaited program drain, failed-program accounting), `test/identity-write-journey.node.test.ts` (committed ambiguity, reconciliation and physical writes), `test/invocation-pipeline.test.ts` (the gate after validation, an unrecorded refusal) |
1386
- | `W10`, `W12` | `test/identity-write-journey.node.test.ts` (real HTTP/MCP and QuickJS: write budget, read-only discovery and refusal, named guide, pool inheritance/reopening and connector policy), `test/program-writes.test.ts` (construction refusals and classification drift), `test/browser/operator-ui.spec.ts` (the badge) |
1387
- | `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (rejected shapes, bounded native snapshots, paging before metadata traversal, and cyclic presentation refusal) |
1388
- | `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) |
1389
- | `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) |
1390
- | `M5`, `M7` | `test/execute-emit.test.ts` (both budgets fail the crossing block; host-call budget untouched) |
1391
- | `M8` | two arms passing one case table, `test/codemode-compat.test.ts` |
1392
- | `M10` | `test/execute-emit.test.ts` (aggregate present, numbers only, absent when nothing emitted) |
1393
- | `X4` | `test/guest-api-contract.test.ts` (string logs only), `test/quickjs-executor.node.test.ts` (logs before cancellation) |
1394
- | `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) |
1395
- | `X7` | `P3`'s tests; the Workers superset is deliberately unused |
1396
-
1397
- The surface itself is checked by `test/server.test.ts` (the exact six-tool list)
1629
+ | Clauses | Test |
1630
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1631
+ | `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`) |
1632
+ | `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/deployment-shapes.node.test.ts` (loader-only Worker construction) |
1633
+ | `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
1634
+ | `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
1635
+ | `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (canonical addressing), `test/server.test.ts` (bounded live connector inventory) |
1636
+ | `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) |
1637
+ | `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
1638
+ | `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) |
1639
+ | `S11` | both guest-contract executors (whole reads, ceiling refusal, stash reclaim), `test/guest-api-contract-quickjs.node.test.ts` (ceiling scaling, default-limits proof), `test/program-result-cases.ts` (sealing composition) |
1640
+ | `S5`, `S6` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`, fail-closed annotations, activity parity) |
1641
+ | `S7` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (parallel calls and shared admission) |
1642
+ | `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) |
1643
+ | `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) |
1644
+ | `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) |
1645
+ | `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` |
1646
+ | `E3`, `E4` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`, destructive reroute), `test/identity-write-journey.node.test.ts` (read-only refusals before validation or dispatch, actual QuickJS) |
1647
+ | `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) |
1648
+ | `E6`, `X8` | `test/guest-api-contract.test.ts` (unknown and inherited members, retained error identities), `test/quickjs-executor.node.test.ts` |
1649
+ | `E7` | `test/guest-api-contract.test.ts` (refusals about a `503`-named connector), `test/errors.test.ts` |
1650
+ | `R1`, `R2`, `R3`, `R4` | `test/guest-api-contract.test.ts` and `test/guest-api-contract-quickjs.node.test.ts` (small pass-through, shared return paging and unavailable fallback, no repeated writes, live authority), `test/program-result-cases.ts` (reconstruction, redaction, notice survives a 2,000-character spill) |
1651
+ | `M6`, `M9` | verdicts; `R2`'s guard, `M1`'s strict typing, and `M2`'s collect-then-deliver are their enforcement |
1652
+ | `R5` | `test/guest-api-contract.test.ts` |
1653
+ | `R6`–`R8` | `test/guest-api-contract.test.ts` (normal result keys), `test/execute.test.ts` (opt-in operation aggregates, failure paths, payload exclusion) |
1654
+ | `Y1`, `Y2`, `Y3` | `test/guest-api-contract.test.ts` (one attempt per call, retryable flags by code) |
1655
+ | `Y4` | `test/meta-tools-call.test.ts`, `test/call-admission.test.ts` (one attempt, retry hints, caller reissue) |
1656
+ | `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) |
1657
+ | `L3`, `X1` | `test/guest-api-contract.test.ts` (short-deadline executors, read caps, write exemption, the write gate, the final dispatch gate, a write on the wire at the ceiling and a stall after a write), `test/node.node.test.ts` (upload counts against the ceiling, arrival comes only from the host), `test/program-writes.test.ts` (what makes a failed program retryable), `test/meta-tools-call.test.ts` (direct and native calls at the final gate), `test/request-admission.test.ts` (a 65 s downstream behind a 60 s client on a fake clock, with the old ceiling as its control), `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) |
1658
+ | `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) |
1659
+ | `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), `test/executor-requests.test.ts` (queued handoffs and refusal shape across separate workerd requests), `test/request-context.node.test.ts` (queued and QuickJS host-call request context) |
1660
+ | `L6`, `X10` | `test/guest-api-contract-quickjs.node.test.ts` and `test/guest-api-contract.test.ts` (whole oversized reads and one-write pageable handles), `test/sqlite-storage.node.test.ts` and `test/storage-sql-d1.test.ts` (SQL stash and native D1 fault probes), `test/quickjs-executor.node.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message) |
1661
+ | `V1`–`V4` | `test/guest-api-contract.test.ts` (dispatched calls, every refusal class including an address no connector owns, the friction each derives, no execution event by default), `test/identity-write-journey.node.test.ts` (opt-in real-program utility/program rows, payload exclusion and detached writes), `test/activity.test.ts` (the shared code → friction table, the identity clamp, the one-attempt floor), `test/browser/operator-ui.spec.ts` (identity changes), `test/sql-storage-contract.ts`, run by `test/sqlite-storage.node.test.ts` (historical activity rows still round-trip) |
1662
+ | `V5`, `W9` | `test/program-writes.test.ts` (unawaited program drain, failed-program accounting), `test/identity-write-journey.node.test.ts` (committed ambiguity, reconciliation and physical writes), `test/invocation-pipeline.test.ts` (the gate after validation, an unrecorded refusal) |
1663
+ | `W10`, `W12` | `test/identity-write-journey.node.test.ts` (real HTTP/MCP and QuickJS: write budget, read-only discovery and refusal, named guide, pool inheritance/reopening and connector policy), `test/program-writes.test.ts` (construction refusals and classification drift), `test/browser/operator-ui.spec.ts` (the badge) |
1664
+ | `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (rejected shapes, bounded native snapshots, paging before metadata traversal, and cyclic presentation refusal) |
1665
+ | `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) |
1666
+ | `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) |
1667
+ | `M5`, `M7` | `test/execute-emit.test.ts` (both budgets fail the crossing block; host-call budget untouched) |
1668
+ | `M8` | two arms passing one case table, `test/codemode-compat.test.ts` |
1669
+ | `M10` | `test/execute-emit.test.ts` (aggregate present, numbers only, absent when nothing emitted) |
1670
+ | `X4` | `test/guest-api-contract.test.ts` (string logs only), `test/quickjs-executor.node.test.ts` (logs before cancellation) |
1671
+ | `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) |
1672
+ | `X7` | `P3`'s tests; the Workers superset is deliberately unused |
1673
+
1674
+ The surface itself is checked by `test/server.test.ts` (the exact tool list)
1398
1675
  and `test/program-writes.test.ts` (deployment policy and classification construction refusals).
1399
1676
 
1400
1677
  ### Adding a guest function
@@ -1407,16 +1684,16 @@ each bridge call by the entry's budget class and binds it by name to a host
1407
1684
  implementation, and the `execute_code` description joins each entry's compact
1408
1685
  declaration and names the functions that spend no host call.
1409
1686
 
1410
- | Function | Budget | Purpose |
1411
- | ----------- | ---------- | -------------------------------------------------------------------------- |
1412
- | `search` | `hostCall` | Searches the catalog and returns one flat page of `{ tools }`. |
1413
- | `describe` | `hostCall` | Describes exact addresses, one entry per address in order. |
1414
- | `call` | `hostCall` | Calls one tool and inlines transfer pages and native content. |
1415
- | `read` | `hostCall` | Reads one advertised resource URI. |
1416
- | `result` | `hostCall` | Pages a retained result by page or UTF-8 byte offset. |
1417
- | `skill` | `hostCall` | Fetches a skill's text by exact name. |
1418
- | `emit` | `emission` | Forwards a content block to the client; needs no await. |
1419
- | `unawaited` | `once` | Tells the host a failed program read a result field off an unawaited call. |
1687
+ | Function | Budget | Purpose |
1688
+ | ----------- | ---------- | ------------------------------------------------------------------------ |
1689
+ | `search` | `hostCall` | Searches the catalog and returns one flat page of `{ tools }`. |
1690
+ | `describe` | `hostCall` | Describes exact addresses, one entry per address in order. |
1691
+ | `call` | `hostCall` | Calls one tool and inlines transfer pages and native content. |
1692
+ | `read` | `hostCall` | Reads one advertised resource URI. |
1693
+ | `result` | `hostCall` | Reads a retained result whole, or pages it by page or UTF-8 byte offset. |
1694
+ | `skill` | `hostCall` | Fetches a skill's text by exact name. |
1695
+ | `emit` | `emission` | Forwards a content block to the client; needs no await. |
1696
+ | `unawaited` | `once` | Tells the host a program read a result field off an unawaited call. |
1420
1697
 
1421
1698
  A `hostCall` function spends one host call on entry (`L4`, `M7`) and returns a
1422
1699
  Promise whose result fields mark an unawaited read. The runtime wraps