@zackbart/connecta 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -6
- package/CHANGELOG.md +56 -0
- package/README.md +15 -19
- package/bin/connecta.mjs +1 -5
- package/dist/activity.d.ts +5 -4
- package/dist/activity.js +1 -10
- package/dist/artifacts/json.js +10 -0
- package/dist/artifacts/operations.js +1 -1
- package/dist/artifacts/validate.js +1 -2
- package/dist/catalog-service.js +1 -1
- package/dist/execute.js +92 -368
- package/dist/executors/quickjs.js +1 -1
- package/dist/exempt-writes.js +162 -0
- package/dist/index.d.ts +12 -31
- package/dist/index.js +14 -47
- package/dist/invocation.js +6 -19
- package/dist/meta-tools.js +1 -2
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/model.d.ts +3 -3
- package/dist/operator-ui/view.js +6 -4
- package/dist/routes/mcp.js +5 -21
- package/dist/routes/shared.d.ts +1 -6
- package/dist/server.js +0 -4
- package/dist/skills.js +8 -23
- package/dist/tool-safety.d.ts +1 -1
- package/dist/tool-safety.js +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +10 -14
- package/documentation/code-mode.md +91 -194
- package/documentation/meta-tools.md +38 -41
- package/ethos.md +6 -7
- package/examples/worker/README.md +10 -14
- package/examples/worker/src/d1-activity-row.ts +4 -1
- package/examples/worker/src/index.ts +1 -9
- package/package.json +2 -2
- package/templates/node/AGENTS.md +4 -3
- package/templates/node/README.md +3 -4
- package/templates/node/package.json +1 -1
- package/dist/resumable.js +0 -1323
- package/dist/run-journal.js +0 -347
|
@@ -16,16 +16,12 @@ 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
|
-
|
|
20
|
-
`
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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.
|
|
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)), as are the
|
|
23
|
+
pause-only `execute.resumableWrites` and `execute.pausedRunTtlSeconds`
|
|
24
|
+
([#672](https://github.com/zackbart/connecta/issues/672)).
|
|
29
25
|
|
|
30
26
|
On Node, install the optional `quickjs-emscripten` peer and use the package's
|
|
31
27
|
QuickJS subpath:
|
|
@@ -141,33 +137,17 @@ storage carried to the next program, and no request-bound object outliving its
|
|
|
141
137
|
request. Within one execution, host calls share one downstream request scope.
|
|
142
138
|
`S9`'s output observation is host-owned catalog metadata, not guest memory — a
|
|
143
139
|
later program receives a labeled field/type schema through discovery, never a
|
|
144
|
-
prior value or object.
|
|
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.
|
|
140
|
+
prior value or object.
|
|
147
141
|
|
|
148
142
|
**P5.** Plain JavaScript only; TypeScript syntax is a syntax error. Portable
|
|
149
143
|
code does not import: QuickJS blocks imports, Dynamic Workers expose the `X5`
|
|
150
144
|
runtime modules, and neither exposes `require`.
|
|
151
145
|
|
|
152
|
-
**P6
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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.
|
|
146
|
+
**P6** pinned a run's clock and randomness so a paused program could replay;
|
|
147
|
+
it left with replay ([#672](https://github.com/zackbart/connecta/issues/672)).
|
|
148
|
+
`Date.now()` and `Math.random()` are the runtime's own again, and the id stays
|
|
149
|
+
retired. QuickJS still runs with `TZ=UTC`, so local time matches a Dynamic
|
|
150
|
+
Worker's (`X5`).
|
|
171
151
|
|
|
172
152
|
## Addressing
|
|
173
153
|
|
|
@@ -263,10 +243,10 @@ integration is ambiguous, because an unscoped search fans out across every
|
|
|
263
243
|
configured connector. `safety: "readOnly"` returns exactly the tools
|
|
264
244
|
`connecta.call` runs unasked; `"approvalRequired"` returns the complementary
|
|
265
245
|
fail-closed class, false, missing, and contradictory annotations included,
|
|
266
|
-
whose calls
|
|
267
|
-
with `approval: "exempt"` without moving it out of that
|
|
268
|
-
`"all"` preserves the complete catalog
|
|
269
|
-
|
|
246
|
+
whose calls a program has refused (`E4`) unless config exempts them (`W12`),
|
|
247
|
+
which a row marks with `approval: "exempt"` without moving it out of that
|
|
248
|
+
class; omitted or `"all"` preserves the complete catalog. These filters grant
|
|
249
|
+
no authority and change no admission decision.
|
|
270
250
|
|
|
271
251
|
**S2.** A requested object schema carries `inputKeys`, `requiredInputKeys`
|
|
272
252
|
(declared properties only), and `outputKeys`: the names the rendered schema
|
|
@@ -335,10 +315,10 @@ is treated as `{}`.
|
|
|
335
315
|
**S6.** Every call goes through the same catalog, fail-closed read-only
|
|
336
316
|
predicate, admission, credential containment, timeout classification, health
|
|
337
317
|
accounting, and activity recording as an ordinary meta-tool call. The predicate
|
|
338
|
-
|
|
339
|
-
it. The sandbox is
|
|
340
|
-
|
|
341
|
-
|
|
318
|
+
and config decide whether a call runs or is refused (`E4`, `W12`); nothing a
|
|
319
|
+
program does decides it. The sandbox is an additional containment layer, not a
|
|
320
|
+
second implementation of the boundary, and nothing a program does widens what
|
|
321
|
+
it can reach.
|
|
342
322
|
|
|
343
323
|
### Parallel calls
|
|
344
324
|
|
|
@@ -413,7 +393,7 @@ caller what to do next, and never invents a cause it was not told.
|
|
|
413
393
|
| --- | --- | --- |
|
|
414
394
|
| `unknown_address` | no connector owns the address | false |
|
|
415
395
|
| `unknown_tool` | the connector has no such tool | false |
|
|
416
|
-
| `destructive_tool_requires_approval` | the tool is
|
|
396
|
+
| `destructive_tool_requires_approval` | the tool is neither explicitly read-only nor config-exempt (`E4`) | false |
|
|
417
397
|
| `auth_required` | the credential is missing, expired, or rejected | false |
|
|
418
398
|
| `invalid_args` | arguments or discovery bounds were rejected | false |
|
|
419
399
|
| `not_found` | the downstream answered and the resource is not there — the one code that says skip this id rather than stop, raised only where the provider tells absence from a permission gap | false |
|
|
@@ -428,19 +408,7 @@ caller what to do next, and never invents a cause it was not told.
|
|
|
428
408
|
| `result_processing_failed` | the result could not be prepared | per message |
|
|
429
409
|
| `result_too_large` | a discovery response exceeded its byte bound | false |
|
|
430
410
|
| `budget_exceeded` | the run exhausted a host-call, write, or emitted-output budget | false |
|
|
431
|
-
| `
|
|
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 |
|
|
411
|
+
| `write_outcome_unknown` | an exempt write was sent and no answer came back; it is never sent again (`W9`) | false |
|
|
444
412
|
|
|
445
413
|
**E3.** `auth_required` carries the same recovery envelope as `call_tool`:
|
|
446
414
|
`connector`, `operation`, `recovery` (`oauth`, `operator_config`, or
|
|
@@ -448,14 +416,15 @@ caller what to do next, and never invents a cause it was not told.
|
|
|
448
416
|
sentence. A program cannot recover credentials — only an operator can — so stop
|
|
449
417
|
and let the failure reach the model.
|
|
450
418
|
|
|
451
|
-
**E4.** An unannotated, write-capable, or destructive tool never runs
|
|
452
|
-
|
|
453
|
-
`destructive_tool_requires_approval
|
|
454
|
-
|
|
455
|
-
the 512-byte echo budget — whole or
|
|
456
|
-
different call. The
|
|
457
|
-
|
|
458
|
-
own write
|
|
419
|
+
**E4.** An unannotated, write-capable, or destructive tool never runs in a
|
|
420
|
+
program unless config exempts it (`W12`). It is refused with
|
|
421
|
+
`destructive_tool_requires_approval` before validation and before anything is
|
|
422
|
+
sent, `nextAction` carrying its canonical address to `call_destructive_tool`
|
|
423
|
+
plus the original arguments when they fit the 512-byte echo budget — whole or
|
|
424
|
+
not at all, since a clipped copy is a different call. The host's prompt on that
|
|
425
|
+
call is the approval, and the only one: generated code can neither mint the
|
|
426
|
+
capability nor approve its own write, and the model's short `reason` grants no
|
|
427
|
+
authority and never goes downstream.
|
|
459
428
|
|
|
460
429
|
**E5.** Failures of the *execution*, not of a call, never appear inside the
|
|
461
430
|
guest: admission rejection (`executor_overloaded`, retryable, with
|
|
@@ -678,7 +647,7 @@ because connecta enforces them above the sandbox:
|
|
|
678
647
|
| Bound | Value |
|
|
679
648
|
| --- | --- |
|
|
680
649
|
| Host calls per execution, shared by `search`, `describe`, and `call` | 20 by default, `execute.maxHostCalls` |
|
|
681
|
-
|
|
|
650
|
+
| Exempt writes per run, on top of the host calls they also spend (`W10`) | 10 by default, `execute.maxWrites` |
|
|
682
651
|
| Deadline per host call | 15 s, `execute.hostCallTimeoutMs`; one deadline covers catalog resolution, admission, and the connector call |
|
|
683
652
|
| Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
|
|
684
653
|
| `describe` addresses | ≤ 100 |
|
|
@@ -693,10 +662,7 @@ unknown tools, catalog failures, and other pre-dispatch refusals cost what a
|
|
|
693
662
|
successful call costs; catching a refusal does not refund it. `search` and
|
|
694
663
|
`describe` likewise spend on entry. Exhausting the budget fails that call with
|
|
695
664
|
non-retryable `budget_exceeded` (`E2`) and a message naming the budget. No
|
|
696
|
-
connector is reached, and the budget does not refill inside one execution.
|
|
697
|
-
replay (`W6`) spends both budgets again for every call it answers from the
|
|
698
|
-
journal, so one play's totals are the run's totals and nothing is counted
|
|
699
|
-
twice.
|
|
665
|
+
connector is reached, and the budget does not refill inside one execution.
|
|
700
666
|
|
|
701
667
|
**L5.** The guest is memory-, stack-, and CPU-bounded, and a program that
|
|
702
668
|
exhausts a bound ends the run with an error instead of degrading the host. The
|
|
@@ -738,12 +704,12 @@ refuses that attempt above the invocation path, so it is charged but records no
|
|
|
738
704
|
event.
|
|
739
705
|
|
|
740
706
|
**V2.** Each event carries `connectorId`, `toolName`, `address`, `source`,
|
|
741
|
-
`outcome` (`success`, `error`, `timeout`, `cancelled
|
|
742
|
-
`
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
707
|
+
`outcome` (`success`, `error`, `timeout`, `cancelled`), `durationMs`,
|
|
708
|
+
`attempts`, and `errorCode` when the call *failed* — plus request, actor, and
|
|
709
|
+
server identity. Rows written before
|
|
710
|
+
[#672](https://github.com/zackbart/connecta/issues/672) may also carry the
|
|
711
|
+
`paused` and `approved` outcomes, `source: "resume_execution"`, and an
|
|
712
|
+
`approval` scope; readers still render them, and nothing emits them. Typed codes derive an optional `friction`: `tool_not_found`,
|
|
747
713
|
`schema_retry`, `destructive_reroute`, or `auth_required`. The fifth class,
|
|
748
714
|
`result_too_large`, cannot reach an `execute_code` event: it belongs to a
|
|
749
715
|
`call_tool` result too large to return inline, and a program's own return is
|
|
@@ -764,114 +730,54 @@ payload-free *by construction* means the event has nowhere to put a payload.
|
|
|
764
730
|
distinctive artifact is the program source — exactly what a payload-free history
|
|
765
731
|
must never keep.
|
|
766
732
|
|
|
767
|
-
**V5.**
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
([#565](https://github.com/zackbart/connecta/issues/565));
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
nextAction, hint } }`, `address` canonical and `args` whole, because the host
|
|
792
|
-
shows the human exactly those; `nextAction` is the resume call. Arguments over
|
|
793
|
-
16 KiB stop the run `pending_write_too_large`, nothing journaled; non-object
|
|
794
|
-
arguments keep `E4`; a `__proto__` key is refused `invalid_args`, since a layer
|
|
795
|
-
between program, host, and human could drop it and approve a different write.
|
|
796
|
-
|
|
797
|
-
**W3.** The journal lives in the caller's result storage — the `get_result`
|
|
798
|
-
stash's partition and trust level — and records the `/mcp/<pool>`: source,
|
|
799
|
-
every host call and its answer, the `P6` clock and seed, and one header, the
|
|
800
|
-
only key compared-and-set. Over 4 MiB fails `journal_too_large`. It expires
|
|
801
|
-
`execute.pausedRunTtlSeconds` (default 1,800) after the first pause, and later
|
|
802
|
-
pauses keep that deadline, so no replay mixes old reads with new. Another
|
|
803
|
-
subject or pool finds no run.
|
|
804
|
-
|
|
805
|
-
**W4.** `resume_execution` takes `token`, `address`, `args`, optional
|
|
806
|
-
`approval`, and a dropped `reason`. Address and args must match the pending
|
|
807
|
-
write as JSON, key order aside, with no `__proto__` key; otherwise it returns
|
|
808
|
-
`approval_mismatch`. That, `execution_token_stale` (current pause),
|
|
809
|
-
`execution_in_progress` (live claim), `execution_expired` (counts if writes
|
|
810
|
-
were sent), and `execution_not_found` refuse before the claim, spending nothing.
|
|
811
|
-
A repeat returns the ended run's answer past expiry; oversized text or emitted blocks get a completion receipt instead.
|
|
812
|
-
|
|
813
|
-
**W5.** An approved write is sent at most once. Resuming claims the run by
|
|
814
|
-
compare-and-set; a racing resume gets `execution_in_progress`. Each live write
|
|
815
|
-
is first marked `sending` on the header, and a claim found moved sends nothing
|
|
816
|
-
further and stops `execution_claim_lost`. The header outlives `expiresAt` while
|
|
817
|
-
a claim is live, and by 30 minutes once a write was sent. A storage call over
|
|
818
|
-
its deadline (the host call's, at least 5 s) fails: before dispatch nothing is
|
|
819
|
-
sent, after it the run stops with the write counted. Replay picks up only at a
|
|
820
|
-
pause: a play ended otherwise — executor failure (admission, cancellation, the
|
|
821
|
-
`L3` watchdog) or a lapsed claim — returns to its pause only if it sent no
|
|
822
|
-
write; a write left `sending` fails `write_outcome_unknown`, and sent writes
|
|
823
|
-
fail `execution_interrupted`, the reads after them never journaled.
|
|
824
|
-
|
|
825
|
-
**W6.** A replay runs from the top on the journal's clock and seed. A recorded
|
|
826
|
-
call — matched by operation, address as written, and canonical arguments, in
|
|
827
|
-
first issue order per key — is answered before the rest of the call path: no
|
|
828
|
-
catalog, permit, connector, or activity (`V5`). A hit is never the pending
|
|
829
|
-
call, which is not journaled. The approved write and everything after it go
|
|
830
|
-
live. `emit` is not journaled; only the completing play delivers.
|
|
831
|
-
|
|
832
|
-
**W7.** `approval: "call"`, the default, covers that one write; `"tool"` covers
|
|
833
|
-
every later call to that canonical address for the run. Approvals live in the
|
|
834
|
-
header, a retry's replacing one that sent nothing; nothing else grants one.
|
|
835
|
-
Keep an authorized batch in one program; use tool scope for repeated calls to that address, while other addresses still need their own approval.
|
|
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
|
|
733
|
+
**V5.** An exempt write a program issues after it has already returned is not
|
|
734
|
+
sent and records nothing: it was never an attempt (`W9`).
|
|
735
|
+
|
|
736
|
+
## Writes
|
|
737
|
+
|
|
738
|
+
A program does not ask anyone anything. It runs explicitly read-only tools and
|
|
739
|
+
the writes config exempts from asking, and refuses every other write (`E4`), so
|
|
740
|
+
each consequential call is its own `call_destructive_tool` and the host's
|
|
741
|
+
permission prompt is the one approval there is. Programs once paused at a write
|
|
742
|
+
and resumed by replay
|
|
743
|
+
([#565](https://github.com/zackbart/connecta/issues/565)); hosts approve one
|
|
744
|
+
call at a time, so under always-allow that prompt approved nothing and cost a
|
|
745
|
+
round trip and a replay, and it went
|
|
746
|
+
([#672](https://github.com/zackbart/connecta/issues/672)). `W1`–`W8` and `W11`
|
|
747
|
+
belonged to pausing and stay retired; nothing reuses those ids.
|
|
748
|
+
|
|
749
|
+
**W9.** An exempt write the program does not await outlives it: the play stops
|
|
750
|
+
gating once the program settles — a write that reaches the gate after is not
|
|
751
|
+
sent (`V5`) — and waits for writes already dispatched, each within its host-call
|
|
752
|
+
deadline, before the run's scope aborts them. A write sent and never answered
|
|
753
|
+
makes the result `write_outcome_unknown` with
|
|
754
|
+
`writes: { succeeded, failed, unknown }`, whatever the program returned, and it
|
|
755
|
+
is never sent again. Only a refusal (`auth_required`, `invalid_args`,
|
|
756
|
+
`not_found`, `rate_limited`, `conflict`, `input_required_unsupported`) or the
|
|
847
757
|
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
|
-
|
|
850
|
-
|
|
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.
|
|
758
|
+
`connector_call_failed` — an oversized body, a refused redirect — may come after
|
|
759
|
+
the write landed. A program that fails after writing carries the same `writes`
|
|
760
|
+
on its error, because it is not a program that is safe to run again.
|
|
856
761
|
|
|
857
|
-
**
|
|
858
|
-
|
|
859
|
-
`
|
|
762
|
+
**W10.** `execute.maxWrites` (default 10) bounds a run's exempt writes beyond
|
|
763
|
+
the host calls they spend, checked at the gate after validation and before
|
|
764
|
+
admission, so an over-budget write fails `budget_exceeded` without a permit.
|
|
765
|
+
The host-call budget bounds fan-out; this one bounds how much a program may
|
|
766
|
+
change unasked, which is a different question even for writes a deployment
|
|
767
|
+
called cheap.
|
|
860
768
|
|
|
861
769
|
**W12.** Config may exempt a write from asking, and nothing else may
|
|
862
770
|
([#566](https://github.com/zackbart/connecta/issues/566)). `execute.approval`
|
|
863
771
|
maps connector ids and `connector.tool` addresses to `"never"` or `"ask"`; the
|
|
864
772
|
address wins over the connector entry, which wins over a connector's own
|
|
865
|
-
built-in default (reserved for connectors connecta ships
|
|
866
|
-
`"ask"`. An exempt call is decided at the
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
downstream could exempt itself. Unknown connectors and `api()` addresses its
|
|
874
|
-
tools lack refuse to construct; an unserved remote address never matches.
|
|
773
|
+
built-in default (reserved for connectors connecta ships, such as artifacts),
|
|
774
|
+
which wins over `"ask"`. An exempt call is decided at the write gate, after
|
|
775
|
+
validation and before admission, and dispatches with the write budget (`W10`),
|
|
776
|
+
`W9`'s tracking, and a `V1` event. Exempt is never read-only: no read-only tool
|
|
777
|
+
is reported exempt, discovery keeps an exempt tool approval-required,
|
|
778
|
+
`call_tool` refuses it, and no annotation grants it, or a downstream could
|
|
779
|
+
exempt itself. Unknown connectors and `api()` addresses its tools lack refuse
|
|
780
|
+
to construct; an unserved remote address never matches.
|
|
875
781
|
|
|
876
782
|
## Executor exceptions
|
|
877
783
|
|
|
@@ -912,7 +818,8 @@ starts has no guest logs to recover.
|
|
|
912
818
|
|
|
913
819
|
**X5. Leftover authority.** QuickJS blocks imports and has no `fetch`,
|
|
914
820
|
`process`, timers, `crypto`, or `WebSocket`, and its Node child starts with an
|
|
915
|
-
explicit environment holding only `TZ=UTC
|
|
821
|
+
explicit environment holding only `TZ=UTC`, so local time matches a Dynamic
|
|
822
|
+
Worker's, rather than inheriting
|
|
916
823
|
deployment variables or `NODE_OPTIONS`. A Dynamic Worker has those globals plus a non-contract set of
|
|
917
824
|
runtime builtins through `import()` and `process.getBuiltinModule()`, including
|
|
918
825
|
`node:path`, `node:crypto`, `node:net`, `node:tls`, `node:dns`, `node:module`,
|
|
@@ -957,11 +864,8 @@ none, because that boundary is an isolate-to-isolate call rather than a
|
|
|
957
864
|
one tool call therefore fails on Node and may succeed on Workers — reduce inside
|
|
958
865
|
the program either way (`R1`).
|
|
959
866
|
|
|
960
|
-
**X12
|
|
961
|
-
|
|
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.
|
|
867
|
+
**X12** described a paused sandbox and left with pausing
|
|
868
|
+
([#672](https://github.com/zackbart/connecta/issues/672)); the id stays retired.
|
|
965
869
|
|
|
966
870
|
**X11. Typed host rejection.** Both executors rebuild Connecta's authenticated
|
|
967
871
|
host-failure frame as a thrown guest `Error` (`E1`). The per-run secret stays in
|
|
@@ -990,7 +894,6 @@ passing one table is also the check on the executor duties above, with
|
|
|
990
894
|
| `P2`, `X5` | `test/guest-api-contract.test.ts` (Dynamic globals plus loader-only filesystem, HTTP, environment, egress, DNS, and local `data:` boundaries), `test/guest-api-contract-quickjs.test.ts` (exact absent globals and blocked imports), `test/quickjs-child-stderr.test.ts` (a child-process environment holding only `TZ=UTC`), `test/deployment-shapes.test.ts` (loader-only Worker construction) |
|
|
991
895
|
| `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
|
|
992
896
|
| `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) |
|
|
994
897
|
| `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (canonical addressing), `test/server.test.ts` (bounded live connector inventory) |
|
|
995
898
|
| `S1`, `S1a`, `S2` | `test/guest-api-contract.test.ts` (flat page, connector guides, schema keys, unfiltered browse), `test/execute.test.ts` (guide pagination/partial/no-match behavior and `$ref`/`allOf`), `test/meta-tools-search.test.ts` (mixed complete/partial ranking, stable pagination, the two safety classes), `test/typescript-signatures.test.ts` (the TypeScript format over provider and pathological schemas, search/describe parity, observed labeling), `test/typescript-signatures-parse.test.ts` (every rendering parses) |
|
|
996
899
|
| `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
|
|
@@ -1000,7 +903,7 @@ passing one table is also the check on the executor duties above, with
|
|
|
1000
903
|
| `S8`, `E1`, `X11` | both guest-contract executors (caught call, discovery, utility, budget, removed-function, and forgery cases; typed promise rejections), `test/quickjs-executor.test.ts` (oversized messages, private transport, forged frames) |
|
|
1001
904
|
| `S9` | `test/result-shapes.test.ts` (value exclusion, bounds, merging, LRU and time expiry, runtime isolation, read-only admission, declared precedence, definition invalidation, unwrapped MCP results, discovery provenance, copy isolation, failure isolation) |
|
|
1002
905
|
| `E2`, `E8` | `test/guest-api-contract.test.ts` (code → `retryable`, caught, parallel, and uncaught validation recovery, a conflict's bounded `current`), `test/meta-tools-call.test.ts` (direct, destructive, provider fallback), `test/validate.test.ts` (bounded payload-free findings), `test/errors.test.ts` |
|
|
1003
|
-
| `E3`, `E4` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`, destructive reroute), `test/
|
|
906
|
+
| `E3`, `E4` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`, destructive reroute), `test/program-writes.test.ts` (the refusal's `nextAction`, nothing sent, a caught refusal) |
|
|
1004
907
|
| `E5` | `test/guest-api-contract.test.ts` (execution-failure channel, in-flight `cancelled`), `test/execute.test.ts` (admission), `test/executor-admission.test.ts`, `test/quickjs-executor.test.ts` (mid-run shutdown) |
|
|
1005
908
|
| `E6`, `X8` | `test/guest-api-contract.test.ts` (unknown and inherited members, wrapped-message precedence), `test/quickjs-executor.test.ts` |
|
|
1006
909
|
| `E7` | `test/guest-api-contract.test.ts` (refusals about a `503`-named connector), `test/errors.test.ts` |
|
|
@@ -1015,16 +918,9 @@ passing one table is also the check on the executor duties above, with
|
|
|
1015
918
|
| `L4`, `L8` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (shared discovery/call budgets) |
|
|
1016
919
|
| `L5`, `L7`, `X2` | `test/quickjs-executor.test.ts` (CPU, heap), `test/execute.test.ts` and `test/executor-admission.test.ts` (bounded admission and queue) |
|
|
1017
920
|
| `L6`, `X10` | `test/quickjs-executor.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message), `test/quickjs-child-stderr.test.ts` (outer reply serialization failure settles the call) |
|
|
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
|
|
1019
|
-
| `V5` | `test/
|
|
1020
|
-
| `
|
|
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) |
|
|
921
|
+
| `V1`–`V4` | `test/guest-api-contract.test.ts` (dispatched calls, every refusal class including an address no connector owns, the friction each derives, no event for the execution itself), `test/activity.test.ts` (the shared code → friction table, the identity clamp, the one-attempt floor), `test/operator-view.test.ts`, `test/d1-activity-example.test.ts` (historical pause and approval rows still render and round-trip) |
|
|
922
|
+
| `V5`, `W9` | `test/program-writes.test.ts` (an unawaited exempt write finished and recorded, its unknown outcome reported, counts on a failed program, the classification table), `test/invocation-pipeline.test.ts` (the gate after validation, an unrecorded refusal) |
|
|
923
|
+
| `W10`, `W12` | `test/program-writes.test.ts` (an exempt write runs and every other write keeps `E4`, the write budget, precedence and a connector default switched off, `call_tool` still refusing, search and describe markers, construction refusals), `test/artifacts-connector.test.ts` (the shipped default and switching it off), `test/operator-ui-model.test.ts`, `test/browser/operator-ui.spec.ts` (the badge) |
|
|
1028
924
|
| `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (every rejected shape) |
|
|
1029
925
|
| `M2`, `M3` | `test/guest-api-contract.test.ts` (delivery order, truncated return plus delivered blocks), `test/execute-emit.test.ts` (envelope, `structuredContent`, byte-for-byte no-emit path) |
|
|
1030
926
|
| `M4` | `test/guest-api-contract.test.ts` (discard is visible), `test/execute-emit.test.ts` (structured and plain paths), `test/quickjs-executor.test.ts` (mid-run shutdown) |
|
|
@@ -1035,5 +931,6 @@ passing one table is also the check on the executor duties above, with
|
|
|
1035
931
|
| `X3`, `X6` | `test/quickjs-executor.test.ts` (cancels a running child, never-settling await), `test/execute.test.ts` (a wedged executor stops being awaited) |
|
|
1036
932
|
| `X7` | `P3`'s tests; the Workers superset is deliberately unused |
|
|
1037
933
|
|
|
1038
|
-
The surface itself is checked by `test/server.test.ts` (the exact
|
|
1039
|
-
`test/code-first-surface.test.ts` (construction, executor, removed tools, copy, and size)
|
|
934
|
+
The surface itself is checked by `test/server.test.ts` (the exact seven-tool list),
|
|
935
|
+
`test/code-first-surface.test.ts` (construction, executor, removed tools, copy, and size),
|
|
936
|
+
and `test/program-writes.test.ts` (the retired pause options refused at construction).
|
|
@@ -2,28 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
Connecta keeps one small tool surface in model context and resolves downstream
|
|
4
4
|
tools behind it. `search_tools` finds addresses, the call tools enforce safety
|
|
5
|
-
annotations, `execute_code` runs work as a program
|
|
6
|
-
|
|
5
|
+
annotations, `execute_code` runs read-only work as a program, and `get_result`
|
|
6
|
+
pages bounded results.
|
|
7
7
|
|
|
8
8
|
This guide is the contract an MCP client sees. The in-program `connecta.*` API
|
|
9
9
|
those tools imply belongs to [code mode](./code-mode.md); inbound identity and
|
|
10
10
|
credential administration belong to [auth](./auth.md).
|
|
11
11
|
|
|
12
|
-
## The
|
|
12
|
+
## The seven tools
|
|
13
13
|
|
|
14
|
-
Every deployment requires an executor, so `tools/list` is exactly
|
|
15
|
-
configuration adds
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
Every deployment requires an executor, so `tools/list` is exactly seven. No
|
|
15
|
+
configuration adds an eighth or removes one, so a client's cached tool list
|
|
16
|
+
never depends on the storage behind the deployment. There was an eighth,
|
|
17
|
+
`resume_execution`, while programs paused at writes; it left with pausing
|
|
18
|
+
([#672](https://github.com/zackbart/connecta/issues/672)). `execute_code`
|
|
19
|
+
keeps its read-only hint because the only writes a program sends are the ones
|
|
20
|
+
the deployment's own config exempted from asking
|
|
21
|
+
([code mode](./code-mode.md#writes) `W12`) — a decision made in code, not by
|
|
22
|
+
the model, and not by a downstream annotation.
|
|
22
23
|
|
|
23
24
|
| Tool | Arguments | Returns |
|
|
24
25
|
| --- | --- | --- |
|
|
25
|
-
| `execute_code` | `code`, `diagnostics?` | the program's reduced return value, plus a `diagnostics` block when asked
|
|
26
|
-
| `resume_execution` | `token`, `address`, `args`, `approval?: "call" \| "tool"`, `reason?` | what `execute_code` would have returned had the write been approved: the result, the next pause, or a typed failure |
|
|
26
|
+
| `execute_code` | `code`, `diagnostics?` | the program's reduced return value, plus a `diagnostics` block when asked |
|
|
27
27
|
| `search_tools` | `query?`, `connector?`, `safety?`, `limit?`, `offset?`, `fullDescriptions?`, `includeSchemas?: "compact" \| "json" \| "typescript"` | `{ connectors: [{ id, tools }], total, offset, limit, hasMore }`, plus `queryAnalysis` on a partial or failed search |
|
|
28
28
|
| `call_tool` | `address`, `args?`, `resultMode?: "mcp" \| "value"`, `timeoutMs?`, `diagnostics?` | the downstream result, bounded as [result representation](#result-representation) describes |
|
|
29
29
|
| `call_destructive_tool` | the same, plus `reason?` | the same |
|
|
@@ -52,13 +52,13 @@ credentials, logs, or raw error text.
|
|
|
52
52
|
|
|
53
53
|
Connecta's own tools carry the annotations it demands of downstream tools:
|
|
54
54
|
read-only hints on all but `authorize_connector`, which mutates stored auth
|
|
55
|
-
state, and the
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
state, and `call_destructive_tool`, the one that sends writes, which is
|
|
56
|
+
destructive. Otherwise a host that gates on annotations would prompt for every
|
|
57
|
+
search, and a connecta aggregated behind another connecta would be refused by
|
|
58
|
+
its own policy. `call_destructive_tool` is where the prompt belongs: its
|
|
59
|
+
arguments are the exact write, so the human approves what will be sent rather
|
|
60
|
+
than a program that might send anything. Its `reason` is dropped before
|
|
61
|
+
anything runs.
|
|
62
62
|
|
|
63
63
|
## Routing between the call surfaces
|
|
64
64
|
|
|
@@ -78,24 +78,21 @@ use a direct `call_destructive_tool` result and page it with `get_result` after
|
|
|
78
78
|
the write runs once. A program return has no page handle; sampling or slicing
|
|
79
79
|
a write's output there can discard the answer.
|
|
80
80
|
|
|
81
|
-
Writes take
|
|
82
|
-
annotated read-only
|
|
83
|
-
exempts that tool, and then it runs unasked —
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
for catalog inspection
|
|
95
|
-
|
|
96
|
-
`destructive_tool_requires_approval`, multi-step writes discover at the top
|
|
97
|
-
level and run each step through `call_destructive_tool`, and the MCP
|
|
98
|
-
instructions say so.
|
|
81
|
+
Writes do not take that route. A program that reaches a tool not explicitly
|
|
82
|
+
annotated read-only refuses it before validation and before anything is sent —
|
|
83
|
+
unless the deployment's config exempts that tool, and then it runs unasked —
|
|
84
|
+
with `destructive_tool_requires_approval` and a `nextAction` naming
|
|
85
|
+
`call_destructive_tool` and the canonical address. Each write is its own
|
|
86
|
+
top-level call, and the host's permission prompt on it is the only approval
|
|
87
|
+
there is. So "close the stale issues and post a summary" is one program to find
|
|
88
|
+
the issues and one `call_destructive_tool` per write; the program does the
|
|
89
|
+
reading, and the host sees every write it is asked about. Programs once paused
|
|
90
|
+
at a write for a resume call to approve and replay; hosts approve one tool call
|
|
91
|
+
at a time, so under always-allow that prompt approved nothing and cost a round
|
|
92
|
+
trip and a replay
|
|
93
|
+
([#672](https://github.com/zackbart/connecta/issues/672)). Top-level discovery
|
|
94
|
+
stays for catalog inspection and for finding the address of a write, and the
|
|
95
|
+
MCP instructions say so.
|
|
99
96
|
|
|
100
97
|
The three discovery routes use deliberately different envelopes. These are their
|
|
101
98
|
smallest successful one-tool shapes:
|
|
@@ -133,7 +130,7 @@ discovery. Both take the same arguments.
|
|
|
133
130
|
| --- | --- |
|
|
134
131
|
| `query` | two to four action/object terms; empty or whitespace-only browses |
|
|
135
132
|
| `connector` | scopes to one id, loading that catalog alone instead of fanning out across every configured connector. Set it when the integration is obvious, omit it when the right one is genuinely ambiguous |
|
|
136
|
-
| `safety` | `"readOnly"` for what runs unasked, `"approvalRequired"` for the complementary set that
|
|
133
|
+
| `safety` | `"readOnly"` for what runs unasked, `"approvalRequired"` for the complementary set that crosses `call_destructive_tool`, omitted or `"all"` for the complete configured catalog. A config-exempt write stays `"approvalRequired"` and its row says `approval: "exempt"`: a program calls it without asking, and `call_tool` still refuses it ([code mode](./code-mode.md#writes) `W12`) |
|
|
137
134
|
| `limit` / `offset` | page the ranked results; omit `limit` initially so the default eight-result page stays small |
|
|
138
135
|
| `includeSchemas` | `"compact"` for the rendered routing view, `"json"` for the exact schema, `"typescript"` for a function signature ([below](#typescript-signatures)) |
|
|
139
136
|
| `fullDescriptions` | unabridged tool purposes, at the obvious cost |
|
|
@@ -542,8 +539,8 @@ answered. The suggested route follows the route the caller took:
|
|
|
542
539
|
the same arguments when the miss happened inside `execute_code`, which has no
|
|
543
540
|
way to call a tool. `call_tool` reaching an unannotated, write-capable, or
|
|
544
541
|
destructive tool returns `nextAction` for `call_destructive_tool` with the
|
|
545
|
-
canonical address
|
|
546
|
-
|
|
542
|
+
canonical address, and so does the same call inside a program, unless config
|
|
543
|
+
exempts the tool there. Nothing is executed by these records.
|
|
547
544
|
|
|
548
545
|
`connecta.describe` keeps its failures inline instead, so one miss cannot
|
|
549
546
|
discard the other schemas; each failed entry carries a human `error`, typed
|
package/ethos.md
CHANGED
|
@@ -5,13 +5,12 @@ Connecta’s scope, refusals, and invariants. Contradictions require a design de
|
|
|
5
5
|
## What this is
|
|
6
6
|
|
|
7
7
|
- **One MCP endpoint.** Agents reach configured connectors through JavaScript,
|
|
8
|
-
a required executor, and
|
|
8
|
+
a required executor, and seven meta-tools.
|
|
9
9
|
- **Config-as-code.** One tenant and connector set; identities get
|
|
10
10
|
config-derived views. Maintained providers, `remoteMcp()`, and `api()` share
|
|
11
11
|
one connector contract.
|
|
12
|
-
- **Safe by default.** Programs run only read-only or config-exempt tools
|
|
13
|
-
|
|
14
|
-
`call_destructive_tool`. The host owns approval.
|
|
12
|
+
- **Safe by default.** Programs run only read-only or config-exempt tools;
|
|
13
|
+
every other write crosses `call_destructive_tool`. The host owns approval.
|
|
15
14
|
- **One fetch-native core.** Web APIs on Node and Workers; platform code and
|
|
16
15
|
optional features behind explicit subpaths; Effect inside, Promises outside.
|
|
17
16
|
- **Human auth management.** Optional pages show status and payload-free history.
|
|
@@ -34,7 +33,7 @@ carries them.
|
|
|
34
33
|
| --- | --- | --- |
|
|
35
34
|
| OpenAPI / GraphQL ingestion | refused | the disease is a document-authored tool nobody chose; hand-written literals, even through a shared factory, are still authorship |
|
|
36
35
|
| Multi-tenancy / account model | refused | one deployment per tenant; inbound auth owns identity |
|
|
37
|
-
| Approvals and pauses |
|
|
36
|
+
| Approvals and pauses | removed | hosts approve per call, so under always-allow the resume prompt was a rubber stamp that cost a round trip and a replay; programs stay read-only and each write is its own `call_destructive_tool` ([#672](https://github.com/zackbart/connecta/issues/672), superseding [#565](https://github.com/zackbart/connecta/issues/565)) |
|
|
38
37
|
| Approval exemptions | accepted | config-only, program-only, never read-only elsewhere ([#566](https://github.com/zackbart/connecta/issues/566)) |
|
|
39
38
|
| Runtime connector registration | refused | config-as-code is the security model |
|
|
40
39
|
| Optional deployment modules | accepted | typed slots select UI, activity, vault, and inbound auth; core keeps discovery, execution, invocation, and enforcement |
|
|
@@ -78,8 +77,8 @@ design decision.
|
|
|
78
77
|
|
|
79
78
|
- **Fail-closed read-only.** A missing, false, or contradictory annotation never gets the benefit of the doubt.
|
|
80
79
|
- **Generated code cannot mint capabilities.** Admission, credentials, and classification are enforced below the sandbox.
|
|
81
|
-
- **Only explicitly read-only or config-exempt work runs
|
|
82
|
-
- **Nothing request-bound survives a request.** No transport, stream, signal, or awaited promise outlives it
|
|
80
|
+
- **Only explicitly read-only or config-exempt work runs in the sandbox.** Every other write crosses `call_destructive_tool`.
|
|
81
|
+
- **Nothing request-bound survives a request.** No transport, stream, signal, or awaited promise outlives it.
|
|
83
82
|
- **A downstream catalog is complete or it is a failure.** A partial one is never cached, persisted, or served.
|
|
84
83
|
- **Activity is payload-free by construction.** The event type has nowhere to put arguments, results, code, or raw errors.
|
|
85
84
|
- **An observed shape is never a declaration.** Names and broad types only, labeled, and gone behind any declared schema.
|