@zackbart/connecta 0.24.4 → 0.26.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 (155) hide show
  1. package/AGENTS.md +43 -12
  2. package/CHANGELOG.md +524 -0
  3. package/README.md +32 -15
  4. package/SECURITY.md +6 -5
  5. package/bin/connecta.mjs +5 -1
  6. package/dist/activity.d.ts +10 -3
  7. package/dist/activity.js +10 -1
  8. package/dist/advertised-schema.js +53 -0
  9. package/dist/artifacts/connector.d.ts +55 -0
  10. package/dist/artifacts/connector.js +724 -0
  11. package/dist/artifacts/document.js +76 -0
  12. package/dist/artifacts/guide.js +140 -0
  13. package/dist/artifacts/html-scan.js +329 -0
  14. package/dist/artifacts/json.js +68 -0
  15. package/dist/artifacts/kv-store.d.ts +20 -0
  16. package/dist/artifacts/kv-store.js +140 -0
  17. package/dist/artifacts/markdown.js +449 -0
  18. package/dist/artifacts/module.d.ts +37 -0
  19. package/dist/artifacts/module.js +60 -0
  20. package/dist/artifacts/operations.d.ts +195 -0
  21. package/dist/artifacts/operations.js +944 -0
  22. package/dist/artifacts/refresh.d.ts +53 -0
  23. package/dist/artifacts/refresh.js +235 -0
  24. package/dist/artifacts/routes.js +194 -0
  25. package/dist/artifacts/types.d.ts +214 -0
  26. package/dist/artifacts/types.js +22 -0
  27. package/dist/artifacts/validate.d.ts +62 -0
  28. package/dist/artifacts/validate.js +652 -0
  29. package/dist/artifacts.d.ts +6 -0
  30. package/dist/artifacts.js +9 -0
  31. package/dist/auth/downstream-oauth.js +490 -106
  32. package/dist/call-admission.js +139 -113
  33. package/dist/catalog-service.js +152 -106
  34. package/dist/catalog.js +297 -38
  35. package/dist/concurrency.js +24 -23
  36. package/dist/connector-access.d.ts +18 -4
  37. package/dist/connector-access.js +65 -20
  38. package/dist/connector-caller.d.ts +8 -0
  39. package/dist/connector-caller.js +19 -0
  40. package/dist/connector-scope.js +6 -37
  41. package/dist/connectors/api.d.ts +2 -2
  42. package/dist/connectors/remote-mcp.d.ts +6 -5
  43. package/dist/connectors/remote-mcp.js +330 -240
  44. package/dist/credential-contract.d.ts +9 -0
  45. package/dist/credential-rules.js +2 -2
  46. package/dist/credentials.d.ts +6 -1
  47. package/dist/credentials.js +45 -3
  48. package/dist/errors.d.ts +16 -1
  49. package/dist/errors.js +28 -0
  50. package/dist/execute.js +745 -282
  51. package/dist/executor-admission.d.ts +11 -1
  52. package/dist/executor-admission.js +145 -63
  53. package/dist/executors/quickjs-runtime.js +4 -2
  54. package/dist/executors/quickjs.js +255 -212
  55. package/dist/fix-prompt.js +37 -0
  56. package/dist/index.d.ts +72 -7
  57. package/dist/index.js +281 -26
  58. package/dist/invocation.js +371 -286
  59. package/dist/meta-tools.js +213 -94
  60. package/dist/module-contracts.d.ts +21 -1
  61. package/dist/oauth-callback-outcome.js +94 -0
  62. package/dist/oauth-sealing.js +29 -0
  63. package/dist/operator-ui/fix-prompts.js +87 -0
  64. package/dist/operator-ui/generated.js +2 -2
  65. package/dist/operator-ui/model.d.ts +103 -1
  66. package/dist/operator-ui/setup-commands.js +53 -0
  67. package/dist/operator-ui/view.js +128 -3
  68. package/dist/program-source.js +321 -0
  69. package/dist/providers/notion.js +2 -2
  70. package/dist/providers/revenuecat.js +32 -5
  71. package/dist/providers/stripe.d.ts +3 -2
  72. package/dist/providers/vercel.js +1 -1
  73. package/dist/registry.d.ts +43 -7
  74. package/dist/registry.js +434 -288
  75. package/dist/resumable.js +1291 -0
  76. package/dist/routes/activity.js +74 -154
  77. package/dist/routes/actor-labels.js +114 -0
  78. package/dist/routes/credentials.js +196 -163
  79. package/dist/routes/mcp.js +228 -112
  80. package/dist/routes/oauth-management.js +73 -71
  81. package/dist/routes/oauth.js +44 -16
  82. package/dist/routes/operator.js +83 -0
  83. package/dist/routes/shared.d.ts +25 -1
  84. package/dist/routes/shared.js +20 -2
  85. package/dist/routes/ui-data.js +211 -0
  86. package/dist/routes/ui.js +76 -35
  87. package/dist/run-journal.js +347 -0
  88. package/dist/runtime/admission.js +43 -0
  89. package/dist/runtime/call-admission.js +67 -0
  90. package/dist/runtime/connector-scope.js +72 -0
  91. package/dist/runtime/run.js +174 -0
  92. package/dist/runtime/services.js +130 -0
  93. package/dist/runtime/shared-read.js +106 -0
  94. package/dist/runtime/storage.js +66 -0
  95. package/dist/server.js +137 -89
  96. package/dist/skills.js +28 -9
  97. package/dist/storage/file.js +39 -2
  98. package/dist/storage/memory.js +28 -16
  99. package/dist/timeout.js +39 -43
  100. package/dist/tool-safety.d.ts +27 -1
  101. package/dist/tool-safety.js +31 -0
  102. package/dist/types.d.ts +45 -7
  103. package/dist/ui.d.ts +25 -4
  104. package/dist/ui.js +77 -167
  105. package/dist/url-safety.js +157 -0
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/documentation/architecture.md +367 -16
  109. package/documentation/auth.md +163 -2
  110. package/documentation/code-mode.md +274 -56
  111. package/documentation/meta-tools.md +200 -44
  112. package/ethos.md +46 -47
  113. package/examples/worker/AGENTS.md +6 -0
  114. package/examples/worker/README.md +224 -14
  115. package/examples/worker/src/d1-activity-row.ts +14 -2
  116. package/examples/worker/src/d1-activity.ts +4 -2
  117. package/examples/worker/src/d1-storage.ts +116 -0
  118. package/examples/worker/src/index.ts +42 -7
  119. package/examples/worker/src/r2-artifact-blobs.ts +29 -0
  120. package/examples/worker/wrangler.jsonc +21 -5
  121. package/package.json +16 -4
  122. package/templates/node/AGENTS.md +9 -2
  123. package/templates/node/README.md +31 -3
  124. package/templates/node/package.json +1 -1
  125. package/templates/node/src/index.ts +27 -0
  126. package/dist/auth/downstream-oauth.d.ts +0 -200
  127. package/dist/catalog-fingerprint.d.ts +0 -19
  128. package/dist/catalog-limits.d.ts +0 -9
  129. package/dist/catalog-service.d.ts +0 -263
  130. package/dist/catalog.d.ts +0 -87
  131. package/dist/concurrency.d.ts +0 -3
  132. package/dist/connectors/guarded-fetch.d.ts +0 -96
  133. package/dist/credential-rules.d.ts +0 -85
  134. package/dist/execute.d.ts +0 -154
  135. package/dist/executor-result.d.ts +0 -10
  136. package/dist/executors/quickjs-child.d.ts +0 -1
  137. package/dist/executors/quickjs-protocol.d.ts +0 -67
  138. package/dist/favicon.d.ts +0 -1
  139. package/dist/identity.d.ts +0 -4
  140. package/dist/invocation.d.ts +0 -74
  141. package/dist/mcp-result.d.ts +0 -8
  142. package/dist/meta-tools.d.ts +0 -129
  143. package/dist/operator-ui/generated.d.ts +0 -2
  144. package/dist/operator-ui/view.d.ts +0 -188
  145. package/dist/result-shapes.d.ts +0 -13
  146. package/dist/retired-toolkits.d.ts +0 -2
  147. package/dist/routes/activity.d.ts +0 -2
  148. package/dist/routes/credentials.d.ts +0 -2
  149. package/dist/routes/mcp.d.ts +0 -10
  150. package/dist/routes/oauth-management.d.ts +0 -2
  151. package/dist/routes/oauth.d.ts +0 -2
  152. package/dist/routes/ui.d.ts +0 -2
  153. package/dist/server.d.ts +0 -9
  154. package/dist/skills.d.ts +0 -44
  155. package/dist/timeout.d.ts +0 -21
@@ -16,10 +16,16 @@ identifiers (`A1`, `E3`, …) are stable and cited by [Verification](#verificati
16
16
  ## Deploy-time capability
17
17
 
18
18
  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
21
- executor is missing, and the removed `surface` option is rejected rather than
22
- ignored ([#273](https://github.com/zackbart/connecta/issues/273)).
19
+ eight — `execute_code`, `resume_execution`, `search_tools`, `call_tool`,
20
+ `call_destructive_tool`, `authorize_connector`, `get_result`, and `skills`.
21
+ Construction fails when the executor is missing, and the removed `surface`
22
+ option is rejected rather than ignored
23
+ ([#273](https://github.com/zackbart/connecta/issues/273)).
24
+
25
+ [Resumable writes](#pausing-and-resuming) need storage with `compareAndSet`:
26
+ omitted, `execute.resumableWrites` is on exactly when the storage has it (not
27
+ Cloudflare KV, which warns once at startup), `true` without it refuses to
28
+ construct, and `/health` reports which.
23
29
 
24
30
  On Node, install the optional `quickjs-emscripten` peer and use the package's
25
31
  QuickJS subpath:
@@ -90,7 +96,9 @@ Connecta passes exactly one provider, named `connecta`. An executor must:
90
96
  5. **Capture `console.log`, `console.warn`, and `console.error`** into `logs` in
91
97
  call order (`R5`), bounding what it retains.
92
98
  6. **Bound the guest**: wall clock, memory, stack, and CPU (`L3`, `L5`), staying
93
- inside the tested `P2`/`X5` boundary.
99
+ inside the tested `P2`/`X5` boundary. Settle `execute()` on your own
100
+ deadline: connecta stops waiting at `execute.watchdogMs` and reports the
101
+ sandbox unresponsive (`L3`).
94
102
  7. **Grant no ambient authority of its own.** Never back this with `eval` or
95
103
  `node:vm`: the sandbox is a containment layer on top of connecta's boundary,
96
104
  not a replacement for it, and every capability arrives through `fns`.
@@ -105,11 +113,15 @@ is just another provider function (`M8`).
105
113
 
106
114
  ## The program
107
115
 
108
- **P1.** A program is one JavaScript `async` arrow-function expression, evaluated
109
- once; its resolved value is the result. Both executors also accept
110
- markdown-fenced code and a bare statement body, normalizing those differently —
111
- a courtesy to model output, not contract, so anything else may be accepted,
112
- rejected, or reinterpreted.
116
+ **P1.** A program is one JavaScript `async` arrow expression, evaluated once;
117
+ its resolved value is the result. Before either executor sees it, `execute_code`
118
+ removes trailing semicolons (and retains comments) so parenthesized evaluation
119
+ works on both. `async () => 1; 2` is still more than one expression. The host
120
+ also recovers one JavaScript fence, even when prose surrounds it, `export default`
121
+ around an async arrow, or one named `async function` declaration with simple
122
+ parameters. The result remains one async arrow. Multiple fences, extra
123
+ statements, and bare statement bodies are outside host recovery. QuickJS may
124
+ wrap a bare body when driven directly; that is outside the portable contract.
113
125
 
114
126
  **P2.** The only capabilities in the contract are `connecta.search`,
115
127
  `connecta.describe`, `connecta.call`, `connecta.emit`, and `console.log` /
@@ -129,12 +141,34 @@ storage carried to the next program, and no request-bound object outliving its
129
141
  request. Within one execution, host calls share one downstream request scope.
130
142
  `S9`'s output observation is host-owned catalog metadata, not guest memory — a
131
143
  later program receives a labeled field/type schema through discovery, never a
132
- prior value or object.
144
+ prior value or object. A run paused at a write leaves a bounded, expiring
145
+ journal (`W3`) for `resume_execution` to replay — host-owned data, not guest
146
+ state: the replay gets back exactly the answers the first play got.
133
147
 
134
148
  **P5.** Plain JavaScript only; TypeScript syntax is a syntax error. Portable
135
149
  code does not import: QuickJS blocks imports, Dynamic Workers expose the `X5`
136
150
  runtime modules, and neither exposes `require`.
137
151
 
152
+ **P6.** A run's clock and randomness are pinned. `Date.now()`, `new Date()`,
153
+ and `Date()` read one instant the host takes at run start, and it does not
154
+ advance; `Date` with arguments, `Date.parse`, and `Date.UTC` are the native
155
+ ones. `Math.random()` is sfc32 seeded from the host's CSPRNG per run. On a
156
+ Dynamic Worker (QuickJS has neither, `X5`), `crypto.getRandomValues` and
157
+ `randomUUID` draw from the same stream and `performance.now()` stays at 0, on
158
+ the objects and their prototypes — so a Workers-only program replays too,
159
+ which makes none of them contract (`P2`). An `Intl` formatter asked for "now"
160
+ formats the pinned instant, and local time is UTC on both executors (the
161
+ QuickJS child runs with `TZ=UTC`). The replacements are trusted prelude,
162
+ locked like `Error` (`X11`), and the native constructor's `now` and prototype
163
+ `constructor` read the same instant; QuickJS lets `defineProperty` replace a
164
+ locked *global* binding, but the replacement is the program's own code. The
165
+ reason is replay (`W6`): a program that branched on a moving clock would take
166
+ another branch the second time and diverge (`W8`), and pinning every run means
167
+ none can tell whether it is replayed. A program cannot time itself; `R7`
168
+ measures from the host. The pin is best-effort beyond these: a program that
169
+ depends on a source it misses, such as the order Workers timers fire in, is
170
+ not portable (`P2`) and fails a replay as divergence.
171
+
138
172
  ## Addressing
139
173
 
140
174
  **A1.** A tool has one canonical address, `<connectorId>.<toolName>`, exactly as
@@ -164,7 +198,7 @@ const page = await connecta.search({
164
198
  limit: 8, // 1–100, default 8
165
199
  offset: 0,
166
200
  fullDescriptions: false,
167
- includeSchemas: "compact", // or "json"
201
+ includeSchemas: "compact", // or "json" / "typescript"
168
202
  includeSchemaKeys: true, // default true in code mode
169
203
  });
170
204
  ```
@@ -192,6 +226,7 @@ bounds:
192
226
 
193
227
  | Bound | Value |
194
228
  | --- | --- |
229
+ | `execute_code` source | ≤ 65,536 UTF-8 bytes, checked before executor admission; over-limit source returns `invalid_args` |
195
230
  | Rendered shape | 1,024 UTF-8 bytes each |
196
231
  | Work per shape | 2,000 visits across schema nodes, property and required names, literal values, any constraint-free retry, and its key-only fallback |
197
232
  | Each enum node | 256 of those bytes |
@@ -211,13 +246,27 @@ truncation flag; `$dynamicRef` resolves a same-named definition like `$ref`, and
211
246
  an unresolved one becomes `unknown` with the flag. For omitted exact constraints
212
247
  use `format: "json"` or JSON search.
213
248
 
249
+ `includeSchemas: "typescript"` spends those same bounds on a different
250
+ rendering: one `signature` string in place of `inputSchema` and
251
+ `outputSchema`, reading `(args: { runId: number }) => Promise<Run>` for the
252
+ value `connecta.call` returns. It is documentation for the program's author —
253
+ the program itself is still JavaScript, and a type annotation copied into it
254
+ is a syntax error (`P5`). Truncation keeps compact's flags and
255
+ turns what compact would print as a name or raw JSON into a marked `unknown`;
256
+ an observed output opens with `/* observed, not declared */`. The dialect and
257
+ its degradation rules are specified once, under
258
+ [TypeScript signatures](./meta-tools.md#typescript-signatures), and the
259
+ signature is byte-identical to the one `search_tools` returns.
260
+
214
261
  **S1a.** `connector` loads only the named catalog; omit it only when the
215
262
  integration is ambiguous, because an unscoped search fans out across every
216
- configured connector. `safety: "readOnly"` returns exactly the tools available
217
- through `connecta.call`; `"approvalRequired"` returns the complementary
218
- fail-closed class, including false, missing, and contradictory annotations;
219
- omitted or `"all"` preserves the complete catalog. These filters grant no
220
- authority and change no admission decision.
263
+ configured connector. `safety: "readOnly"` returns exactly the tools
264
+ `connecta.call` runs unasked; `"approvalRequired"` returns the complementary
265
+ fail-closed class, false, missing, and contradictory annotations included,
266
+ whose calls pause (`W1`) unless config exempts them (`W12`), which a row marks
267
+ with `approval: "exempt"` without moving it out of that class; omitted or
268
+ `"all"` preserves the complete catalog, as a program that means to write needs.
269
+ These filters grant no authority and change no admission decision.
221
270
 
222
271
  **S2.** A requested object schema carries `inputKeys`, `requiredInputKeys`
223
272
  (declared properties only), and `outputKeys`: the names the rendered schema
@@ -249,7 +298,7 @@ hint and the stable `code`, `retryable`, and `details` fields (`E1`).
249
298
  const one = await connecta.describe({ address: "ci.get_run" });
250
299
  const many = await connecta.describe({
251
300
  addresses: ["ci.get_run", "ci.get_job_logs"], // ≤ 100
252
- format: "compact", // or "json"
301
+ format: "compact", // or "json" / "typescript"
253
302
  fullDescriptions: false,
254
303
  });
255
304
  ```
@@ -265,8 +314,11 @@ canonical addresses. More than 100 addresses is `invalid_args`, and the same
265
314
  256,000-byte ceiling applies. Compact describe keeps property prose within a
266
315
  separate 8,192-byte UTF-8 shape cap and shares search's 2,000-visit budget; a
267
316
  capped shape sets `inputSchemaTruncated` or `outputSchemaTruncated`, and
268
- `format: "json"` gives the exact schema. A success whose output shape came from
269
- `S9` carries `outputSchemaSource: "observed"`.
317
+ `format: "json"` gives the exact schema. `format: "typescript"` returns a
318
+ `signature` instead of both schema fields, under the same 8,192-byte cap per
319
+ half with property prose as JSDoc. A success whose output shape came from
320
+ `S9` carries `outputSchemaSource: "observed"`, and a TypeScript signature also
321
+ says so inside its `Promise<…>`.
270
322
 
271
323
  ### connecta.call
272
324
 
@@ -282,7 +334,9 @@ is treated as `{}`.
282
334
 
283
335
  **S6.** Every call goes through the same catalog, fail-closed read-only
284
336
  predicate, admission, credential containment, timeout classification, health
285
- accounting, and activity recording as an ordinary meta-tool call. The sandbox is
337
+ accounting, and activity recording as an ordinary meta-tool call. The predicate
338
+ decides whether a call runs or pauses (`W1`); nothing a program does decides
339
+ it. The sandbox is
286
340
  an additional containment layer, not a second implementation of the boundary,
287
341
  and nothing a program does widens what it can reach.
288
342
 
@@ -359,10 +413,11 @@ caller what to do next, and never invents a cause it was not told.
359
413
  | --- | --- | --- |
360
414
  | `unknown_address` | no connector owns the address | false |
361
415
  | `unknown_tool` | the connector has no such tool | false |
362
- | `destructive_tool_requires_approval` | the tool is not explicitly read-only | false |
416
+ | `destructive_tool_requires_approval` | the tool is not explicitly read-only and the deployment has no resumable writes (`E4`) | false |
363
417
  | `auth_required` | the credential is missing, expired, or rejected | false |
364
418
  | `invalid_args` | arguments or discovery bounds were rejected | false |
365
419
  | `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 |
420
+ | `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 |
366
421
  | `input_required_unsupported` | a downstream asked for mid-call input | false |
367
422
  | `rate_limited` | the downstream reported a rate limit | true |
368
423
  | `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 |
@@ -372,7 +427,20 @@ caller what to do next, and never invents a cause it was not told.
372
427
  | `catalog_lookup_failed` | the connector's catalog could not be loaded | per cause |
373
428
  | `result_processing_failed` | the result could not be prepared | per message |
374
429
  | `result_too_large` | a discovery response exceeded its byte bound | false |
375
- | `budget_exceeded` | the run exhausted a host-call or emitted-output budget | false |
430
+ | `budget_exceeded` | the run exhausted a host-call, write, or emitted-output budget | false |
431
+ | `execution_paused` | the run already stopped at a write that needs approval; no further call is made (`W1`) | false |
432
+ | `pending_write_too_large` | a write's arguments are over 16 KiB serialized, too large to hold for approval (`W2`) | false |
433
+ | `journal_too_large` | the run's source and recorded calls are over 4 MiB, too much to pause (`W3`) | false |
434
+ | `write_outcome_unknown` | a write was sent and no answer came back; it is never sent again (`W9`) | false |
435
+ | `execution_interrupted` | a resumed play sent writes and then ended without pausing, or storage failed under it; the run cannot be resumed (`W5`) | false |
436
+ | `execution_diverged` | a replay stopped matching its journal (`W8`) | false |
437
+ | `execution_claim_lost` | another `resume_execution` took the run over mid-play (`W5`) | false |
438
+ | `approval_mismatch` | `resume_execution` did not repeat the paused write exactly (`W4`) | false |
439
+ | `execution_token_stale` | the token names an earlier pause of a run that has moved on (`W4`) | false |
440
+ | `execution_in_progress` | another `resume_execution` is playing the run (`W5`) | true |
441
+ | `execution_expired` | the paused run outlived `execute.pausedRunTtlSeconds`, or reached another write after it (`W3`) | false |
442
+ | `execution_not_found` | no paused run matches the token in this caller's partition and pool (`W3`) | false |
443
+ | `resumable_writes_unavailable` | the deployment has no resumable writes, so nothing can be resumed (`W11`) | false |
376
444
 
377
445
  **E3.** `auth_required` carries the same recovery envelope as `call_tool`:
378
446
  `connector`, `operation`, `recovery` (`oauth`, `operator_config`, or
@@ -380,17 +448,20 @@ caller what to do next, and never invents a cause it was not told.
380
448
  sentence. A program cannot recover credentials — only an operator can — so stop
381
449
  and let the failure reach the model.
382
450
 
383
- **E4.** An unannotated, write-capable, or destructive tool stays refused with
384
- `destructive_tool_requires_approval`; `nextAction` carries its canonical address
385
- to `call_destructive_tool`, plus the original arguments when they fit the
386
- 512-byte echo budget — whole or not at all, since a clipped copy is a different
387
- call. The model's short `reason` for the human reviewer grants no authority,
388
- never goes downstream, and generated code cannot mint the capability.
451
+ **E4.** An unannotated, write-capable, or destructive tool never runs unasked:
452
+ it pauses the run (`W1`), or, without resumable writes, is refused with
453
+ `destructive_tool_requires_approval`, `nextAction` carrying its canonical
454
+ address to `call_destructive_tool` plus the original arguments when they fit
455
+ the 512-byte echo budget — whole or not at all, since a clipped copy is a
456
+ different call. The model's short `reason` grants no authority and never goes
457
+ downstream, and generated code can neither mint the capability nor approve its
458
+ own write.
389
459
 
390
460
  **E5.** Failures of the *execution*, not of a call, never appear inside the
391
461
  guest: admission rejection (`executor_overloaded`, retryable, with
392
462
  `retryAfterMs`), cancellation (`executor_cancelled`), shutdown
393
- (`executor_closed`), deadline expiry, and sandbox crashes end the run and reach
463
+ (`executor_closed`), deadline expiry, an executor that never settles (`L3`),
464
+ and sandbox crashes end the run and reach
394
465
  the model as an error result. One seam: a host call still in flight when the run
395
466
  is cancelled fails with `cancelled`, catchable on the way out but never worth
396
467
  acting on (`Y3`). When shutdown tears down a program that had already started,
@@ -582,12 +653,25 @@ block runs — a cancelled QuickJS child is terminated outright. Write programs
582
653
  that need no cleanup.
583
654
 
584
655
  **L2.** What cancellation guarantees: in-flight host calls abort, no further
585
- host call is admitted, the admission lease is released, and nothing
586
- request-bound survives the request.
656
+ host call is admitted — `search` and `describe` included, which fail
657
+ `cancelled` rather than go on loading catalogs — the response returns
658
+ `executor_cancelled` without waiting for the executor to settle, or for an
659
+ `acquire()` that ignores the signal to grant, the admission lease is released
660
+ (a lease granted after the run gave up is released on arrival), and nothing
661
+ request-bound survives the request. Whether the program itself stops is the
662
+ executor's (`X3`).
587
663
 
588
664
  **L3.** Every execution runs under a wall-clock deadline that includes time
589
665
  spent waiting on host calls. Expiry ends the run with an execution error and no
590
- partial result; the deadline's length is executor configuration (`X1`).
666
+ partial result; the deadline's length is executor configuration (`X1`). Above
667
+ it sits a ceiling connecta enforces outside the sandbox, `execute.watchdogMs`
668
+ (120 s by default, above both executors' own deadlines), because an executor's
669
+ deadline may live inside the sandbox it is meant to stop: a wedged Dynamic
670
+ Worker never fires its in-isolate timer. A run whose executor has not settled
671
+ by the ceiling ends as a non-retryable `executor_failed` whose message calls
672
+ the sandbox unresponsive, with no partial result, and its admission lease is
673
+ released, so wedged runs cannot fill the code pool. Raising an executor's
674
+ deadline past the ceiling means raising the ceiling too.
591
675
 
592
676
  **L4.** Per-execution bounds that are contract, identical in both executors
593
677
  because connecta enforces them above the sandbox:
@@ -595,6 +679,7 @@ because connecta enforces them above the sandbox:
595
679
  | Bound | Value |
596
680
  | --- | --- |
597
681
  | Host calls per execution, shared by `search`, `describe`, and `call` | 20 by default, `execute.maxHostCalls` |
682
+ | Writes per run, on top of the host calls they also spend (`W10`) | 10 by default, `execute.maxWrites` |
598
683
  | Deadline per host call | 15 s, `execute.hostCallTimeoutMs`; one deadline covers catalog resolution, admission, and the connector call |
599
684
  | Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
600
685
  | `describe` addresses | ≤ 100 |
@@ -609,7 +694,10 @@ unknown tools, catalog failures, and other pre-dispatch refusals cost what a
609
694
  successful call costs; catching a refusal does not refund it. `search` and
610
695
  `describe` likewise spend on entry. Exhausting the budget fails that call with
611
696
  non-retryable `budget_exceeded` (`E2`) and a message naming the budget. No
612
- connector is reached, and the budget does not refill inside one execution.
697
+ connector is reached, and the budget does not refill inside one execution. A
698
+ replay (`W6`) spends both budgets again for every call it answers from the
699
+ journal, so one play's totals are the run's totals and nothing is counted
700
+ twice.
613
701
 
614
702
  **L5.** The guest is memory-, stack-, and CPU-bounded, and a program that
615
703
  exhausts a bound ends the run with an error instead of degrading the host. The
@@ -651,9 +739,12 @@ refuses that attempt above the invocation path, so it is charged but records no
651
739
  event.
652
740
 
653
741
  **V2.** Each event carries `connectorId`, `toolName`, `address`, `source`,
654
- `outcome` (`success`, `error`, `timeout`, `cancelled`), `durationMs`,
655
- `attempts`, and `errorCode` when the call *failed* — plus request, actor, and
656
- server identity. Typed codes derive an optional `friction`: `tool_not_found`,
742
+ `outcome` (`success`, `error`, `timeout`, `cancelled`, `paused`, `approved`),
743
+ `durationMs`, `attempts`, and `errorCode` when the call *failed* — plus
744
+ request, actor, and server identity. `paused` and `approved` name the write a
745
+ run stopped at and the one `resume_execution` approved, with zero attempts;
746
+ an approval adds `approval: "call" | "tool"`, never the arguments. A resumed
747
+ play's live calls carry `source: "resume_execution"`. Typed codes derive an optional `friction`: `tool_not_found`,
657
748
  `schema_retry`, `destructive_reroute`, or `auth_required`. The fifth class,
658
749
  `result_too_large`, cannot reach an `execute_code` event: it belongs to a
659
750
  `call_tool` result too large to return inline, and a program's own return is
@@ -674,6 +765,114 @@ payload-free *by construction* means the event has nowhere to put a payload.
674
765
  distinctive artifact is the program source — exactly what a payload-free history
675
766
  must never keep.
676
767
 
768
+ **V5.** A replayed call records nothing: it was recorded when it happened, and
769
+ a second event would count one call twice. Nor does a call refused because its
770
+ run had already stopped, which was never an attempt.
771
+
772
+ ## Pausing and resuming
773
+
774
+ A program pauses host-side at its first write needing approval;
775
+ `resume_execution` repeats that write to approve it, and the run replays from a
776
+ journal to send it and carry on. A paused run is data, not a held program, so a
777
+ restart loses nothing
778
+ ([#565](https://github.com/zackbart/connecta/issues/565)); `ExecuteResult` and
779
+ the executor duties are unchanged.
780
+
781
+ **W1.** A call to a tool not explicitly read-only reaches the run's write gate
782
+ after validation and before admission, so nobody approves arguments the schema
783
+ rejects and a pause costs no permit. Unless an approval covers it, it is not
784
+ sent: it rejects `execution_paused`, as does every later host call —
785
+ unjournaled, unrecorded — while calls in flight finish and are journaled; one
786
+ of those turning unknown fails the run instead (`W9`). The program's result is
787
+ discarded, and a write that reaches the gate after it settles is not sent.
788
+ Calls are numbered in issue order and a write decides only after every
789
+ lower-numbered call has, so the pause point is reproducible.
790
+
791
+ **W2.** The pause is a success: `{ paused: { address, args, token, expiresAt,
792
+ nextAction, hint } }`, `address` canonical and `args` whole, because the host
793
+ shows the human exactly those; `nextAction` is the resume call. Arguments over
794
+ 16 KiB stop the run `pending_write_too_large`, nothing journaled; non-object
795
+ arguments keep `E4`; a `__proto__` key is refused `invalid_args`, since a layer
796
+ between program, host, and human could drop it and approve a different write.
797
+
798
+ **W3.** The journal lives in the caller's result storage — the `get_result`
799
+ stash's partition and trust level — and records the `/mcp/<pool>`: source,
800
+ every host call and its answer, the `P6` clock and seed, and one header, the
801
+ only key compared-and-set. Over 4 MiB fails `journal_too_large`. It expires
802
+ `execute.pausedRunTtlSeconds` (default 1,800) after the first pause, and later
803
+ pauses keep that deadline, so no replay mixes old reads with new. Another
804
+ subject or pool finds no run.
805
+
806
+ **W4.** `resume_execution` takes `token`, `address`, `args`, optional
807
+ `approval`, and a dropped `reason`. Address and args must match the pending
808
+ write as JSON, key order aside, with no `__proto__` key; otherwise it returns
809
+ `approval_mismatch`. That, `execution_token_stale` (current pause),
810
+ `execution_in_progress` (live claim), `execution_expired` (counts if writes
811
+ were sent), and `execution_not_found` refuse before the claim, spending nothing.
812
+ A repeat returns the ended run's answer past expiry; oversized text or emitted blocks get a completion receipt instead.
813
+
814
+ **W5.** An approved write is sent at most once. Resuming claims the run by
815
+ compare-and-set; a racing resume gets `execution_in_progress`. Each live write
816
+ is first marked `sending` on the header, and a claim found moved sends nothing
817
+ further and stops `execution_claim_lost`. The header outlives `expiresAt` while
818
+ a claim is live, and by 30 minutes once a write was sent. A storage call over
819
+ its deadline (the host call's, at least 5 s) fails: before dispatch nothing is
820
+ sent, after it the run stops with the write counted. Replay picks up only at a
821
+ pause: a play ended otherwise — executor failure (admission, cancellation, the
822
+ `L3` watchdog) or a lapsed claim — returns to its pause only if it sent no
823
+ write; a write left `sending` fails `write_outcome_unknown`, and sent writes
824
+ fail `execution_interrupted`, the reads after them never journaled.
825
+
826
+ **W6.** A replay runs from the top on the journal's clock and seed. A recorded
827
+ call — matched by operation, address as written, and canonical arguments, in
828
+ first issue order per key — is answered before the rest of the call path: no
829
+ catalog, permit, connector, or activity (`V5`). A hit is never the pending
830
+ call, which is not journaled. The approved write and everything after it go
831
+ live. `emit` is not journaled; only the completing play delivers.
832
+
833
+ **W7.** `approval: "call"`, the default, covers that one write; `"tool"` covers
834
+ every later call to that canonical address for the run. Approvals live in the
835
+ header, a retry's replacing one that sent nothing; nothing else grants one.
836
+
837
+ **W8.** A replay fails `execution_diverged`, never replayed again, when (a) a
838
+ call has no record before the approved write is repeated, (b) the program
839
+ finishes without repeating it, or (c) without repeating a write already sent.
840
+
841
+ **W9.** A write sent and never answered fails the run `write_outcome_unknown`
842
+ wherever it is found — beside a pause, after the program returned, at a
843
+ takeover — with its address, its args within the 512-byte echo budget, and
844
+ `writes: { succeeded, failed, unknown }`; a failed run is never replayed. Only
845
+ a refusal (`auth_required`, `invalid_args`, `not_found`, `rate_limited`,
846
+ `conflict`) or the
847
+ tool's own `isError` answers; an `isError` reading as a timeout does not, and
848
+ `connector_call_failed` — an oversized body, a refused redirect — may come
849
+ after the write landed. Every error from a resumed play carries `writes`, and
850
+ once one landed or may have, `nextAction` says to check first; a program that
851
+ throws after writing ends the run `failed` with them.
852
+
853
+ **W10.** `execute.maxWrites` (default 10) bounds a run's writes beyond the host
854
+ calls they spend, checked at the gate — so an over-budget write fails
855
+ `budget_exceeded` instead of asking — and spent at dispatch.
856
+
857
+ **W11.** Without resumable writes — storage without `compareAndSet`, or
858
+ `execute.resumableWrites: false` — `E4` stands and `resume_execution` answers
859
+ `resumable_writes_unavailable`.
860
+
861
+ **W12.** Config may exempt a write from asking, and nothing else may
862
+ ([#566](https://github.com/zackbart/connecta/issues/566)). `execute.approval`
863
+ maps connector ids and `connector.tool` addresses to `"never"` or `"ask"`; the
864
+ address wins over the connector entry, which wins over a connector's own
865
+ built-in default (reserved for connectors connecta ships), which wins over
866
+ `"ask"`. An exempt call is decided at the same gate as `W1` and dispatches
867
+ instead of pausing, with everything else an approved write gets: the write
868
+ budget (`W10`), the write-ahead mark (`W5`), the journal, a `V1` event. Without
869
+ resumable writes, other writes keep `E4`; an exempt one still finishes after
870
+ the program returns, never hiding an unknown outcome (`W9`). Exempt is never
871
+ read-only: no read-only tool is reported exempt, discovery keeps an exempt tool
872
+ approval-required, `call_tool` refuses it, and no annotation grants it, or a
873
+ downstream could exempt itself. Unknown connectors and `api()` addresses its
874
+ tools lack refuse to construct; an unserved remote address never matches.
875
+
677
876
  ## Executor exceptions
678
877
 
679
878
  Documented divergences, with reasons. Everything else must match.
@@ -688,10 +887,15 @@ and guest-CPU limits (`L5`); the Dynamic Worker has no such knobs, so workerd's
688
887
  isolate limits apply untuned. A specific heap ceiling is a Node-only option.
689
888
 
690
889
  **X3. Mid-flight cancellation.** The QuickJS pool receives the request's
691
- `AbortSignal` and kills the child. The Dynamic Worker executor's `execute()`
692
- takes no signal, so a cancelled request's program runs on until its host calls
693
- fail or the deadline expires. `L2` holds either way — the calls abort, the
694
- response does not wait — but "the run ends" is best-effort on Workers.
890
+ `AbortSignal` and kills the child — SIGTERM, then SIGKILL if it has not exited
891
+ within a second — and releasing a lease whose child is still running recycles
892
+ that child, so a run the watchdog abandons (`L3`) ends too.
893
+ The Dynamic Worker executor's `execute()` takes no signal, so a cancelled or
894
+ abandoned program runs on in its isolate until its host calls fail or its own
895
+ deadline expires. `L2` and `L3` hold either way — the calls abort, connecta
896
+ stops awaiting the executor and frees the admission slot — but "the run ends"
897
+ is best-effort on Workers, and for that tail the code pool bounds what
898
+ connecta is waiting on, not what the platform is still running.
695
899
 
696
900
  **X4. Log rendering and capture.** QuickJS JSON-stringifies non-string arguments
697
901
  and captures `log`, `info`, `warn`, `error`, and `debug`; the Dynamic Worker
@@ -708,8 +912,8 @@ starts has no guest logs to recover.
708
912
 
709
913
  **X5. Leftover authority.** QuickJS blocks imports and has no `fetch`,
710
914
  `process`, timers, `crypto`, or `WebSocket`, and its Node child starts with an
711
- explicitly empty process environment rather than inheriting deployment variables
712
- or `NODE_OPTIONS`. A Dynamic Worker has those globals plus a non-contract set of
915
+ explicit environment holding only `TZ=UTC` (`P6`) rather than inheriting
916
+ deployment variables or `NODE_OPTIONS`. A Dynamic Worker has those globals plus a non-contract set of
713
917
  runtime builtins through `import()` and `process.getBuiltinModule()`, including
714
918
  `node:path`, `node:crypto`, `node:net`, `node:tls`, `node:dns`, `node:module`,
715
919
  and `cloudflare:workers`; the upstream set drifts, so that is not an allowlist.
@@ -753,6 +957,12 @@ none, because that boundary is an isolate-to-isolate call rather than a
753
957
  one tool call therefore fails on Node and may succeed on Workers — reduce inside
754
958
  the program either way (`R1`).
755
959
 
960
+ **X12. A paused sandbox.** QuickJS stops: releasing the lease with the child
961
+ still running recycles it (`X3`). A Dynamic Worker cannot be interrupted, so a
962
+ paused program runs on until it settles or its deadline expires, every host
963
+ call failing `execution_paused`. Connecta stops awaiting it at the pause and
964
+ discards whatever it returns.
965
+
756
966
  **X11. Typed host rejection.** Both executors rebuild Connecta's authenticated
757
967
  host-failure frame as a thrown guest `Error` (`E1`). The per-run secret stays in
758
968
  the trusted prelude closure and the prelude locks `globalThis.Error`, so guest
@@ -776,20 +986,21 @@ passing one table is also the check on the executor duties above, with
776
986
 
777
987
  | Clauses | Test |
778
988
  | --- | --- |
779
- | `P1`, `P5` | `test/guest-api-contract.test.ts` (TypeScript syntax), `test/quickjs-executor.test.ts` (`normalizeCode`) |
780
- | `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` (empty child-process environment), `test/deployment-shapes.test.ts` (loader-only Worker construction) |
989
+ | `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`) |
990
+ | `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) |
781
991
  | `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
782
992
  | `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
993
+ | `P6` | `test/guest-api-contract.test.ts` (a frozen clock across host calls, the seeded sfc32 stream against a reference, locked replacements, Workers `crypto` and `performance` pinned directly and through their prototypes, `Intl` "now" and UTC local time on both executors, a fresh pair per run, and a replay drawing the same values), `test/resumable-restart.test.ts` (the same draws after a restart) |
783
994
  | `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (canonical addressing), `test/server.test.ts` (bounded live connector inventory) |
784
- | `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) |
995
+ | `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) |
785
996
  | `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
786
997
  | `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) |
787
998
  | `S5`, `S6` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`, fail-closed annotations, activity parity) |
788
999
  | `S7` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (parallel calls and shared admission) |
789
1000
  | `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) |
790
1001
  | `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) |
791
- | `E2`, `E8` | `test/guest-api-contract.test.ts` (code → `retryable`, caught, parallel, and uncaught validation recovery), `test/meta-tools-call.test.ts` (direct, destructive, provider fallback), `test/validate.test.ts` (bounded payload-free findings), `test/errors.test.ts` |
792
- | `E3`, `E4` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`, destructive reroute) |
1002
+ | `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` |
1003
+ | `E3`, `E4` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`, destructive reroute), `test/resumable.test.ts` (the refusal without resumable writes) |
793
1004
  | `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) |
794
1005
  | `E6`, `X8` | `test/guest-api-contract.test.ts` (unknown and inherited members, wrapped-message precedence), `test/quickjs-executor.test.ts` |
795
1006
  | `E7` | `test/guest-api-contract.test.ts` (refusals about a `503`-named connector), `test/errors.test.ts` |
@@ -799,12 +1010,21 @@ passing one table is also the check on the executor duties above, with
799
1010
  | `R6`–`R8` | `test/guest-api-contract.test.ts` (normal result keys), `test/execute.test.ts` (opt-in operation aggregates, failure paths, payload exclusion) |
800
1011
  | `Y1`, `Y2`, `Y3` | `test/guest-api-contract.test.ts` (one attempt per call, retryable flags by code) |
801
1012
  | `Y4` | `test/meta-tools-call.test.ts`, `test/call-admission.test.ts` (one attempt, retry hints, caller reissue) |
802
- | `L1`, `L2` | `test/guest-api-contract.test.ts` (in-flight call fails `cancelled`), `test/execute.test.ts` (cancels outstanding host calls) |
803
- | `L3`, `X1` | `test/guest-api-contract.test.ts` (short-deadline executors) |
1013
+ | `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) |
1014
+ | `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) |
804
1015
  | `L4`, `L8` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (shared discovery/call budgets) |
805
1016
  | `L5`, `L7`, `X2` | `test/quickjs-executor.test.ts` (CPU, heap), `test/execute.test.ts` and `test/executor-admission.test.ts` (bounded admission and queue) |
806
1017
  | `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) |
807
- | `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) |
1018
+ | `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, `paused` and `approved` events), `test/activity.test.ts` (the shared code → friction table, the identity clamp, zero-attempt pauses and approvals) |
1019
+ | `V5` | `test/resumable.test.ts` (replay adds only the approval and the live write), `test/guest-api-contract.test.ts` |
1020
+ | `W1`, `W2` | `test/guest-api-contract.test.ts` (nothing after a pause gets through, a `__proto__` write refused, on both executors), `test/resumable.test.ts` (the pause shape, reproducible pause points, oversized pending writes, a write gated after the program returned), `test/invocation-pipeline.test.ts` (the gate after validation) |
1021
+ | `W3`, `W4` | `test/resumable.test.ts` (header layout, byte bound, expiry, not-found, pool isolation, exact-match refusals that consume nothing, a `__proto__` repetition through the MCP schema, a repeated resume and emitted-content receipt, an expired run's counts) |
1022
+ | `W5` | `test/resumable.test.ts` (two resumes racing for a pause, a crashed claimant's `sending` write never sent, a lapsed claim taken over, a claim lost mid-play and the write it sent reported, executor failure returning to the pause, a cut-short or lapsed play that sent writes failing `execution_interrupted`, a claim outliving `expiresAt`, a live or lapsed claim checked before expiry, a failed takeover's answer stored with it, storage that never answers before and after dispatch, a deadline between mark and dispatch) |
1023
+ | `W6`, `W7` | `test/guest-api-contract.test.ts` (replay on both executors), `test/resumable.test.ts` (no catalog, connector, or activity traffic for replayed calls; call and tool scope, a retry's scope replacing an unused approval, a hit never taken for the pending call), `test/resumable-restart.test.ts` (across a restart) |
1024
+ | `W8`–`W10` | `test/resumable.test.ts` (divergence (a)–(c), each unknown-outcome path never re-sent — beside a concurrent pause, after the program returned, a post-response connector error — a known failure the program handles, the classification table, check-first advice once a write landed, the write budget) |
1025
+ | `W11` | `test/code-first-surface.test.ts`, `test/resumable.test.ts` (construction, the default, `/health`, the unavailable answer) |
1026
+ | `W12` | `test/resumable.test.ts` (an exempt write runs where the same program otherwise pauses, precedence and a connector default switched off, the write budget, resumable writes off, an unawaited exempt write finished and its unknown outcome reported, journaled and never re-sent, an exempt write's count past expiry, `call_tool` still refusing, search and describe markers, construction refusals), `test/operator-ui-model.test.ts`, `test/browser/operator-ui.spec.ts` (the badge) |
1027
+ | `X12` | `test/guest-api-contract.test.ts` (a program that keeps calling after the pause, on both executors) |
808
1028
  | `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (every rejected shape) |
809
1029
  | `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) |
810
1030
  | `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) |
@@ -812,10 +1032,8 @@ passing one table is also the check on the executor duties above, with
812
1032
  | `M8` | two arms passing one case table, `test/codemode-compat.test.ts` |
813
1033
  | `M10` | `test/execute-emit.test.ts` (aggregate present, numbers only, absent when nothing emitted) |
814
1034
  | `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) |
815
- | `X3`, `X6` | `test/quickjs-executor.test.ts` (cancels a running child, never-settling await) |
1035
+ | `X3`, `X6` | `test/quickjs-executor.test.ts` (cancels a running child, never-settling await), `test/execute.test.ts` (a wedged executor stops being awaited) |
816
1036
  | `X7` | `P3`'s tests; the Workers superset is deliberately unused |
817
1037
 
818
- The surface itself is checked by `test/server.test.ts` (the exact seven-tool
819
- list) and `test/code-first-surface.test.ts` (the fold's construction rules, the
820
- required executor, the refusals a removed top-level tool now gets, copy, and
821
- measured size).
1038
+ The surface itself is checked by `test/server.test.ts` (the exact eight-tool list) and
1039
+ `test/code-first-surface.test.ts` (construction, executor, removed tools, copy, and size).