@zackbart/connecta 0.25.0 → 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.
- package/AGENTS.md +6 -5
- package/CHANGELOG.md +271 -0
- package/README.md +32 -15
- package/SECURITY.md +6 -5
- package/bin/connecta.mjs +5 -1
- package/dist/activity.d.ts +10 -3
- package/dist/activity.js +10 -1
- package/dist/artifacts/connector.d.ts +55 -0
- package/dist/artifacts/connector.js +724 -0
- package/dist/artifacts/document.js +76 -0
- package/dist/artifacts/guide.js +140 -0
- package/dist/artifacts/html-scan.js +329 -0
- package/dist/artifacts/json.js +68 -0
- package/dist/artifacts/kv-store.d.ts +20 -0
- package/dist/artifacts/kv-store.js +140 -0
- package/dist/artifacts/markdown.js +449 -0
- package/dist/artifacts/module.d.ts +37 -0
- package/dist/artifacts/module.js +60 -0
- package/dist/artifacts/operations.d.ts +195 -0
- package/dist/artifacts/operations.js +944 -0
- package/dist/artifacts/refresh.d.ts +53 -0
- package/dist/artifacts/refresh.js +235 -0
- package/dist/artifacts/routes.js +194 -0
- package/dist/artifacts/types.d.ts +214 -0
- package/dist/artifacts/types.js +22 -0
- package/dist/artifacts/validate.d.ts +62 -0
- package/dist/artifacts/validate.js +652 -0
- package/dist/artifacts.d.ts +6 -0
- package/dist/artifacts.js +9 -0
- package/dist/catalog-service.js +39 -29
- package/dist/catalog.js +42 -8
- package/dist/connector-access.d.ts +18 -4
- package/dist/connector-access.js +65 -20
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +19 -0
- package/dist/connectors/remote-mcp.js +16 -2
- package/dist/errors.d.ts +16 -1
- package/dist/errors.js +28 -0
- package/dist/execute.js +427 -70
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +79 -14
- package/dist/executors/quickjs.js +4 -2
- package/dist/index.d.ts +62 -6
- package/dist/index.js +230 -6
- package/dist/invocation.js +56 -10
- package/dist/meta-tools.js +7 -2
- package/dist/module-contracts.d.ts +21 -1
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +62 -5
- package/dist/operator-ui/view.js +94 -5
- package/dist/program-source.js +116 -0
- package/dist/registry.d.ts +42 -6
- package/dist/registry.js +148 -47
- package/dist/resumable.js +1291 -0
- package/dist/routes/activity.js +6 -69
- package/dist/routes/actor-labels.js +114 -0
- package/dist/routes/credentials.js +27 -5
- package/dist/routes/mcp.js +116 -59
- package/dist/routes/oauth-management.js +22 -9
- package/dist/routes/operator.js +1 -0
- package/dist/routes/shared.d.ts +23 -1
- package/dist/routes/shared.js +20 -2
- package/dist/routes/ui-data.js +2 -1
- package/dist/routes/ui.js +13 -4
- package/dist/run-journal.js +347 -0
- package/dist/runtime/shared-read.js +106 -0
- package/dist/server.js +26 -0
- package/dist/skills.js +27 -8
- package/dist/timeout.js +26 -0
- package/dist/tool-safety.d.ts +35 -0
- package/dist/tool-safety.js +31 -0
- package/dist/types.d.ts +18 -0
- package/dist/ui.d.ts +12 -5
- package/dist/ui.js +32 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +196 -19
- package/documentation/auth.md +83 -2
- package/documentation/code-mode.md +221 -48
- package/documentation/meta-tools.md +44 -25
- package/ethos.md +12 -10
- package/examples/worker/AGENTS.md +6 -0
- package/examples/worker/README.md +174 -10
- package/examples/worker/src/d1-activity-row.ts +14 -2
- package/examples/worker/src/d1-activity.ts +4 -2
- package/examples/worker/src/index.ts +36 -1
- package/examples/worker/src/r2-artifact-blobs.ts +29 -0
- package/examples/worker/wrangler.jsonc +5 -1
- package/package.json +6 -2
- package/templates/node/AGENTS.md +9 -2
- package/templates/node/README.md +31 -3
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +27 -0
|
@@ -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
|
-
|
|
20
|
-
`authorize_connector`, `get_result`, and `skills`.
|
|
21
|
-
executor is missing, and the removed `surface`
|
|
22
|
-
ignored
|
|
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:
|
|
@@ -107,16 +113,15 @@ is just another provider function (`M8`).
|
|
|
107
113
|
|
|
108
114
|
## The program
|
|
109
115
|
|
|
110
|
-
**P1.** A program is one JavaScript `async` arrow
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
not contract, so anything else may be accepted, 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.
|
|
120
125
|
|
|
121
126
|
**P2.** The only capabilities in the contract are `connecta.search`,
|
|
122
127
|
`connecta.describe`, `connecta.call`, `connecta.emit`, and `console.log` /
|
|
@@ -136,14 +141,34 @@ storage carried to the next program, and no request-bound object outliving its
|
|
|
136
141
|
request. Within one execution, host calls share one downstream request scope.
|
|
137
142
|
`S9`'s output observation is host-owned catalog metadata, not guest memory — a
|
|
138
143
|
later program receives a labeled field/type schema through discovery, never a
|
|
139
|
-
prior value or object.
|
|
140
|
-
(
|
|
141
|
-
|
|
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.
|
|
142
147
|
|
|
143
148
|
**P5.** Plain JavaScript only; TypeScript syntax is a syntax error. Portable
|
|
144
149
|
code does not import: QuickJS blocks imports, Dynamic Workers expose the `X5`
|
|
145
150
|
runtime modules, and neither exposes `require`.
|
|
146
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
|
+
|
|
147
172
|
## Addressing
|
|
148
173
|
|
|
149
174
|
**A1.** A tool has one canonical address, `<connectorId>.<toolName>`, exactly as
|
|
@@ -201,6 +226,7 @@ bounds:
|
|
|
201
226
|
|
|
202
227
|
| Bound | Value |
|
|
203
228
|
| --- | --- |
|
|
229
|
+
| `execute_code` source | ≤ 65,536 UTF-8 bytes, checked before executor admission; over-limit source returns `invalid_args` |
|
|
204
230
|
| Rendered shape | 1,024 UTF-8 bytes each |
|
|
205
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 |
|
|
206
232
|
| Each enum node | 256 of those bytes |
|
|
@@ -234,11 +260,13 @@ signature is byte-identical to the one `search_tools` returns.
|
|
|
234
260
|
|
|
235
261
|
**S1a.** `connector` loads only the named catalog; omit it only when the
|
|
236
262
|
integration is ambiguous, because an unscoped search fans out across every
|
|
237
|
-
configured connector. `safety: "readOnly"` returns exactly the tools
|
|
238
|
-
|
|
239
|
-
fail-closed class,
|
|
240
|
-
|
|
241
|
-
|
|
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.
|
|
242
270
|
|
|
243
271
|
**S2.** A requested object schema carries `inputKeys`, `requiredInputKeys`
|
|
244
272
|
(declared properties only), and `outputKeys`: the names the rendered schema
|
|
@@ -306,7 +334,9 @@ is treated as `{}`.
|
|
|
306
334
|
|
|
307
335
|
**S6.** Every call goes through the same catalog, fail-closed read-only
|
|
308
336
|
predicate, admission, credential containment, timeout classification, health
|
|
309
|
-
accounting, and activity recording as an ordinary meta-tool call. The
|
|
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
|
|
310
340
|
an additional containment layer, not a second implementation of the boundary,
|
|
311
341
|
and nothing a program does widens what it can reach.
|
|
312
342
|
|
|
@@ -383,10 +413,11 @@ caller what to do next, and never invents a cause it was not told.
|
|
|
383
413
|
| --- | --- | --- |
|
|
384
414
|
| `unknown_address` | no connector owns the address | false |
|
|
385
415
|
| `unknown_tool` | the connector has no such tool | false |
|
|
386
|
-
| `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 |
|
|
387
417
|
| `auth_required` | the credential is missing, expired, or rejected | false |
|
|
388
418
|
| `invalid_args` | arguments or discovery bounds were rejected | false |
|
|
389
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 |
|
|
390
421
|
| `input_required_unsupported` | a downstream asked for mid-call input | false |
|
|
391
422
|
| `rate_limited` | the downstream reported a rate limit | true |
|
|
392
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 |
|
|
@@ -396,7 +427,20 @@ caller what to do next, and never invents a cause it was not told.
|
|
|
396
427
|
| `catalog_lookup_failed` | the connector's catalog could not be loaded | per cause |
|
|
397
428
|
| `result_processing_failed` | the result could not be prepared | per message |
|
|
398
429
|
| `result_too_large` | a discovery response exceeded its byte bound | false |
|
|
399
|
-
| `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 |
|
|
400
444
|
|
|
401
445
|
**E3.** `auth_required` carries the same recovery envelope as `call_tool`:
|
|
402
446
|
`connector`, `operation`, `recovery` (`oauth`, `operator_config`, or
|
|
@@ -404,14 +448,14 @@ caller what to do next, and never invents a cause it was not told.
|
|
|
404
448
|
sentence. A program cannot recover credentials — only an operator can — so stop
|
|
405
449
|
and let the failure reach the model.
|
|
406
450
|
|
|
407
|
-
**E4.** An unannotated, write-capable, or destructive tool
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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.
|
|
415
459
|
|
|
416
460
|
**E5.** Failures of the *execution*, not of a call, never appear inside the
|
|
417
461
|
guest: admission rejection (`executor_overloaded`, retryable, with
|
|
@@ -635,6 +679,7 @@ because connecta enforces them above the sandbox:
|
|
|
635
679
|
| Bound | Value |
|
|
636
680
|
| --- | --- |
|
|
637
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` |
|
|
638
683
|
| Deadline per host call | 15 s, `execute.hostCallTimeoutMs`; one deadline covers catalog resolution, admission, and the connector call |
|
|
639
684
|
| Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
|
|
640
685
|
| `describe` addresses | ≤ 100 |
|
|
@@ -649,7 +694,10 @@ unknown tools, catalog failures, and other pre-dispatch refusals cost what a
|
|
|
649
694
|
successful call costs; catching a refusal does not refund it. `search` and
|
|
650
695
|
`describe` likewise spend on entry. Exhausting the budget fails that call with
|
|
651
696
|
non-retryable `budget_exceeded` (`E2`) and a message naming the budget. No
|
|
652
|
-
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.
|
|
653
701
|
|
|
654
702
|
**L5.** The guest is memory-, stack-, and CPU-bounded, and a program that
|
|
655
703
|
exhausts a bound ends the run with an error instead of degrading the host. The
|
|
@@ -691,9 +739,12 @@ refuses that attempt above the invocation path, so it is charged but records no
|
|
|
691
739
|
event.
|
|
692
740
|
|
|
693
741
|
**V2.** Each event carries `connectorId`, `toolName`, `address`, `source`,
|
|
694
|
-
`outcome` (`success`, `error`, `timeout`, `cancelled`),
|
|
695
|
-
`attempts`, and `errorCode` when the call *failed* — plus
|
|
696
|
-
server identity.
|
|
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`,
|
|
697
748
|
`schema_retry`, `destructive_reroute`, or `auth_required`. The fifth class,
|
|
698
749
|
`result_too_large`, cannot reach an `execute_code` event: it belongs to a
|
|
699
750
|
`call_tool` result too large to return inline, and a program's own return is
|
|
@@ -714,6 +765,114 @@ payload-free *by construction* means the event has nowhere to put a payload.
|
|
|
714
765
|
distinctive artifact is the program source — exactly what a payload-free history
|
|
715
766
|
must never keep.
|
|
716
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
|
+
|
|
717
876
|
## Executor exceptions
|
|
718
877
|
|
|
719
878
|
Documented divergences, with reasons. Everything else must match.
|
|
@@ -753,8 +912,8 @@ starts has no guest logs to recover.
|
|
|
753
912
|
|
|
754
913
|
**X5. Leftover authority.** QuickJS blocks imports and has no `fetch`,
|
|
755
914
|
`process`, timers, `crypto`, or `WebSocket`, and its Node child starts with an
|
|
756
|
-
|
|
757
|
-
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
|
|
758
917
|
runtime builtins through `import()` and `process.getBuiltinModule()`, including
|
|
759
918
|
`node:path`, `node:crypto`, `node:net`, `node:tls`, `node:dns`, `node:module`,
|
|
760
919
|
and `cloudflare:workers`; the upstream set drifts, so that is not an allowlist.
|
|
@@ -798,6 +957,12 @@ none, because that boundary is an isolate-to-isolate call rather than a
|
|
|
798
957
|
one tool call therefore fails on Node and may succeed on Workers — reduce inside
|
|
799
958
|
the program either way (`R1`).
|
|
800
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
|
+
|
|
801
966
|
**X11. Typed host rejection.** Both executors rebuild Connecta's authenticated
|
|
802
967
|
host-failure frame as a thrown guest `Error` (`E1`). The per-run secret stays in
|
|
803
968
|
the trusted prelude closure and the prelude locks `globalThis.Error`, so guest
|
|
@@ -821,10 +986,11 @@ passing one table is also the check on the executor duties above, with
|
|
|
821
986
|
|
|
822
987
|
| Clauses | Test |
|
|
823
988
|
| --- | --- |
|
|
824
|
-
| `P1`, `P5` |
|
|
825
|
-
| `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` (
|
|
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) |
|
|
826
991
|
| `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
|
|
827
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) |
|
|
828
994
|
| `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (canonical addressing), `test/server.test.ts` (bounded live connector inventory) |
|
|
829
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) |
|
|
830
996
|
| `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
|
|
@@ -833,8 +999,8 @@ passing one table is also the check on the executor duties above, with
|
|
|
833
999
|
| `S7` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (parallel calls and shared admission) |
|
|
834
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) |
|
|
835
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) |
|
|
836
|
-
| `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` |
|
|
837
|
-
| `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) |
|
|
838
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) |
|
|
839
1005
|
| `E6`, `X8` | `test/guest-api-contract.test.ts` (unknown and inherited members, wrapped-message precedence), `test/quickjs-executor.test.ts` |
|
|
840
1006
|
| `E7` | `test/guest-api-contract.test.ts` (refusals about a `503`-named connector), `test/errors.test.ts` |
|
|
@@ -849,7 +1015,16 @@ passing one table is also the check on the executor duties above, with
|
|
|
849
1015
|
| `L4`, `L8` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (shared discovery/call budgets) |
|
|
850
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) |
|
|
851
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) |
|
|
852
|
-
| `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) |
|
|
853
1028
|
| `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (every rejected shape) |
|
|
854
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) |
|
|
855
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) |
|
|
@@ -860,7 +1035,5 @@ passing one table is also the check on the executor duties above, with
|
|
|
860
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) |
|
|
861
1036
|
| `X7` | `P3`'s tests; the Workers superset is deliberately unused |
|
|
862
1037
|
|
|
863
|
-
The surface itself is checked by `test/server.test.ts` (the exact
|
|
864
|
-
|
|
865
|
-
required executor, the refusals a removed top-level tool now gets, copy, and
|
|
866
|
-
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).
|
|
@@ -2,24 +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
|
|
6
|
-
pages bounded results.
|
|
5
|
+
annotations, `execute_code` runs work as a program that pauses at each write
|
|
6
|
+
until `resume_execution` approves it, and `get_result` 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 eight tools
|
|
13
13
|
|
|
14
|
-
Every deployment requires an executor, so `tools/list` is exactly
|
|
15
|
-
configuration adds
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
Every deployment requires an executor, so `tools/list` is exactly eight. No
|
|
15
|
+
configuration adds a ninth or removes one — not even turning resumable writes
|
|
16
|
+
off, which changes what `resume_execution` answers and nothing about whether it
|
|
17
|
+
is listed, so a client's cached tool list never depends on the storage behind
|
|
18
|
+
the deployment. `execute_code` keeps its read-only hint although its programs
|
|
19
|
+
may now reach writes, because no write runs there: a program stops before
|
|
20
|
+
sending one, and the write runs only inside the destructive-annotated
|
|
21
|
+
`resume_execution` that repeats it ([code mode](./code-mode.md#pausing-and-resuming)).
|
|
19
22
|
|
|
20
23
|
| Tool | Arguments | Returns |
|
|
21
24
|
| --- | --- | --- |
|
|
22
|
-
| `execute_code` | `code`, `diagnostics?` | the program's reduced return value, plus a `diagnostics` block when asked |
|
|
25
|
+
| `execute_code` | `code`, `diagnostics?` | the program's reduced return value, plus a `diagnostics` block when asked; or, when it reached a write, `{ paused: { address, args, token, expiresAt, nextAction, hint } }` |
|
|
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 |
|
|
23
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 |
|
|
24
28
|
| `call_tool` | `address`, `args?`, `resultMode?: "mcp" \| "value"`, `timeoutMs?`, `diagnostics?` | the downstream result, bounded as [result representation](#result-representation) describes |
|
|
25
29
|
| `call_destructive_tool` | the same, plus `reason?` | the same |
|
|
@@ -48,9 +52,13 @@ credentials, logs, or raw error text.
|
|
|
48
52
|
|
|
49
53
|
Connecta's own tools carry the annotations it demands of downstream tools:
|
|
50
54
|
read-only hints on all but `authorize_connector`, which mutates stored auth
|
|
51
|
-
state, and
|
|
52
|
-
|
|
53
|
-
would
|
|
55
|
+
state, and the two that send writes, `call_destructive_tool` and
|
|
56
|
+
`resume_execution`, both destructive. Otherwise a host that gates on
|
|
57
|
+
annotations would prompt for every search, and a connecta aggregated behind
|
|
58
|
+
another connecta would be refused by its own policy. `resume_execution` is
|
|
59
|
+
where the prompt belongs: its arguments are the exact write, so the human
|
|
60
|
+
approves what will be sent rather than a program that might send anything.
|
|
61
|
+
Its `reason`, like `call_destructive_tool`'s, is dropped before anything runs.
|
|
54
62
|
|
|
55
63
|
## Routing between the call surfaces
|
|
56
64
|
|
|
@@ -66,12 +74,21 @@ as a small sample for inspection before continuing in another call, which avoids
|
|
|
66
74
|
repeated guesses at text formats and collection roots without restoring a
|
|
67
75
|
mandatory discovery-only round trip.
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
Writes take the same route. A program that reaches a tool not explicitly
|
|
78
|
+
annotated read-only stops before sending it — unless the deployment's config
|
|
79
|
+
exempts that tool, and then it runs unasked — and returns the exact call with a
|
|
80
|
+
token; `resume_execution` repeating it is the approval, and the program
|
|
81
|
+
replays from a journal to send it and carry on — to its answer, or to its next
|
|
82
|
+
write. So "close the stale issues and post a summary" is one program and a few
|
|
83
|
+
approvals, not one program and thirty-one top-level calls, and the program's
|
|
84
|
+
reasoning survives between them. `approval: "tool"` covers the rest of the
|
|
85
|
+
run's calls to that tool, so thirty closes cost one prompt. One known write
|
|
86
|
+
still goes straight to `call_destructive_tool`, and top-level discovery stays
|
|
87
|
+
for catalog inspection. On a deployment without resumable writes (storage
|
|
88
|
+
without `compareAndSet`), a program cannot write at all: the call fails
|
|
89
|
+
`destructive_tool_requires_approval`, multi-step writes discover at the top
|
|
90
|
+
level and run each step through `call_destructive_tool`, and the MCP
|
|
91
|
+
instructions say so.
|
|
75
92
|
|
|
76
93
|
The three discovery routes use deliberately different envelopes. These are their
|
|
77
94
|
smallest successful one-tool shapes:
|
|
@@ -109,7 +126,7 @@ discovery. Both take the same arguments.
|
|
|
109
126
|
| --- | --- |
|
|
110
127
|
| `query` | two to four action/object terms; empty or whitespace-only browses |
|
|
111
128
|
| `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 |
|
|
112
|
-
| `safety` | `"readOnly"` for what
|
|
129
|
+
| `safety` | `"readOnly"` for what runs unasked, `"approvalRequired"` for the complementary set that pauses a program or 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 pausing, and `call_tool` still refuses it ([code mode](./code-mode.md#pausing-and-resuming) `W12`) |
|
|
113
130
|
| `limit` / `offset` | page the ranked results; omit `limit` initially so the default eight-result page stays small |
|
|
114
131
|
| `includeSchemas` | `"compact"` for the rendered routing view, `"json"` for the exact schema, `"typescript"` for a function signature ([below](#typescript-signatures)) |
|
|
115
132
|
| `fullDescriptions` | unabridged tool purposes, at the obvious cost |
|
|
@@ -173,10 +190,11 @@ The walk, byte budgets, and truncation flags are compact's, each half
|
|
|
173
190
|
separately, and search, describe, and every discovery route share them, so the
|
|
174
191
|
same tool renders the same signature from `search_tools` and `connecta.search`.
|
|
175
192
|
What changes is the dialect: `number` for `integer`, `;` between members,
|
|
176
|
-
quoted non-identifier keys, `
|
|
177
|
-
|
|
178
|
-
`
|
|
179
|
-
|
|
193
|
+
quoted non-identifier keys, `| null` for OpenAPI `nullable` and type lists,
|
|
194
|
+
index signatures for schema-valued `additionalProperties`,
|
|
195
|
+
`Record<string, unknown>` for an open object with no declared properties, and
|
|
196
|
+
JSDoc for property prose where describe keeps it. Both dialects parenthesize a
|
|
197
|
+
union or intersection before an array suffix, `(A | B)[]`.
|
|
180
198
|
Where compact would print a bare definition name or raw JSON, the signature
|
|
181
199
|
prints `unknown` with a marker and sets the flag: `/* recursive */` for a
|
|
182
200
|
`$ref` cycle, `/* unresolved */` for a missing target, `/* truncated */` past
|
|
@@ -515,9 +533,10 @@ attempted tool name; an unknown tool scopes the same query to the connector that
|
|
|
515
533
|
answered. The suggested route follows the route the caller took:
|
|
516
534
|
`tool: "search_tools"` for a top-level call, `function: "connecta.search"` with
|
|
517
535
|
the same arguments when the miss happened inside `execute_code`, which has no
|
|
518
|
-
way to call a tool.
|
|
536
|
+
way to call a tool. `call_tool` reaching an unannotated, write-capable, or
|
|
519
537
|
destructive tool returns `nextAction` for `call_destructive_tool` with the
|
|
520
|
-
canonical address
|
|
538
|
+
canonical address; inside a program the same call pauses, and the pause result
|
|
539
|
+
names `resume_execution` instead. Nothing is executed by these records.
|
|
521
540
|
|
|
522
541
|
`connecta.describe` keeps its failures inline instead, so one miss cannot
|
|
523
542
|
discard the other schemas; each failed entry carries a human `error`, typed
|
package/ethos.md
CHANGED
|
@@ -5,12 +5,13 @@ 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 eight 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
|
|
13
|
-
others
|
|
12
|
+
- **Safe by default.** Programs run only read-only or config-exempt tools
|
|
13
|
+
unasked; others pause for `resume_execution` or cross
|
|
14
|
+
`call_destructive_tool`. The host owns approval.
|
|
14
15
|
- **One fetch-native core.** Web APIs on Node and Workers; platform code and
|
|
15
16
|
optional features behind explicit subpaths; Effect inside, Promises outside.
|
|
16
17
|
- **Human auth management.** Optional pages show status and payload-free history.
|
|
@@ -33,10 +34,11 @@ carries them.
|
|
|
33
34
|
| --- | --- | --- |
|
|
34
35
|
| OpenAPI / GraphQL ingestion | refused | the disease is a document-authored tool nobody chose; hand-written literals, even through a shared factory, are still authorship |
|
|
35
36
|
| Multi-tenancy / account model | refused | one deployment per tenant; inbound auth owns identity |
|
|
36
|
-
| Approvals and pauses |
|
|
37
|
+
| Approvals and pauses | accepted | a program pauses host-side at its first unapproved write until the destructive-annotated `resume_execution` repeats it; an expiring journal replays it, never re-sending unknown outcomes ([#565](https://github.com/zackbart/connecta/issues/565)) |
|
|
38
|
+
| Approval exemptions | accepted | config-only, program-only, never read-only elsewhere ([#566](https://github.com/zackbart/connecta/issues/566)) |
|
|
37
39
|
| Runtime connector registration | refused | config-as-code is the security model |
|
|
38
40
|
| Optional deployment modules | accepted | typed slots select UI, activity, vault, and inbound auth; core keeps discovery, execution, invocation, and enforcement |
|
|
39
|
-
| Artifacts module |
|
|
41
|
+
| Artifacts module | accepted | a built-in connector for team-only sandboxed pages over stored JSON; immutable versions let writes skip approval. Supersedes [#287](https://github.com/zackbart/connecta/issues/287) |
|
|
40
42
|
| Plugin lifecycle, provider registry, or marketplace | refused | modules are deployment code, not runtime installs; prebuilt connections are imports, discovered in docs ([#297](https://github.com/zackbart/connecta/issues/297)) |
|
|
41
43
|
| Connecta-issued access tokens | removed | inbound providers authenticate clients; bearer auth stays an optional adapter |
|
|
42
44
|
| Expanded Notion page create/update options | refused | different workflows, not missing fields; use `api()` ([#408](https://github.com/zackbart/connecta/issues/408)) |
|
|
@@ -57,12 +59,12 @@ carries them.
|
|
|
57
59
|
| Connector shortcut globals | removed | canonical addresses need no sanitization |
|
|
58
60
|
| `connecta.batch` | removed | JavaScript promises suffice |
|
|
59
61
|
| Automatic direct-call retries | removed | callers own retry timing |
|
|
60
|
-
| Connector HTTP routes | removed | deployments own custom routes; artifact
|
|
62
|
+
| Connector HTTP routes | removed | deployments own custom routes; the operator UI's artifact viewer is the exception |
|
|
61
63
|
| Caller-selected toolkits | removed | config derives every view, grant-gated `/mcp/<pool>` pools included ([#178](https://github.com/zackbart/connecta/issues/178), [#531](https://github.com/zackbart/connecta/issues/531)) |
|
|
62
64
|
| Proactive credential liveness | removed | fail-at-use is enough ([#179](https://github.com/zackbart/connecta/issues/179)) |
|
|
63
65
|
| Classic (executor-free) surface | removed | an executor is mandatory ([#273](https://github.com/zackbart/connecta/issues/273)) |
|
|
64
66
|
| Per-result lexical query coverage | removed | failed a precommitted response-bytes gate ([#323](https://github.com/zackbart/connecta/issues/323)) |
|
|
65
|
-
| Stabilized workflows |
|
|
67
|
+
| Stabilized workflows | accepted | only as artifact refresh: deployment-triggered read-only programs, shared credentials ([#225](https://github.com/zackbart/connecta/issues/225), [#564](https://github.com/zackbart/connecta/issues/564)) |
|
|
66
68
|
| Semantic tool search | gated | keyword search has not been shown to fail ([#27](https://github.com/zackbart/connecta/issues/27)) |
|
|
67
69
|
| MRTR / `input_required` passthrough | gated | relayable statelessly; no host or downstream emits it yet ([#176](https://github.com/zackbart/connecta/issues/176)) |
|
|
68
70
|
| Downstream `ttlMs` cache hints | gated | needs refresh-churn evidence ([#206](https://github.com/zackbart/connecta/issues/206)) |
|
|
@@ -76,7 +78,7 @@ design decision.
|
|
|
76
78
|
|
|
77
79
|
- **Fail-closed read-only.** A missing, false, or contradictory annotation never gets the benefit of the doubt.
|
|
78
80
|
- **Generated code cannot mint capabilities.** Admission, credentials, and classification are enforced below the sandbox.
|
|
79
|
-
- **Only explicitly read-only work runs unasked in the sandbox.**
|
|
81
|
+
- **Only explicitly read-only or config-exempt work runs unasked in the sandbox.** Others pause or cross `call_destructive_tool`.
|
|
80
82
|
- **Nothing request-bound survives a request.** No transport, stream, signal, or awaited promise outlives it; a paused journal is data, not a request.
|
|
81
83
|
- **A downstream catalog is complete or it is a failure.** A partial one is never cached, persisted, or served.
|
|
82
84
|
- **Activity is payload-free by construction.** The event type has nowhere to put arguments, results, code, or raw errors.
|
|
@@ -84,8 +86,8 @@ design decision.
|
|
|
84
86
|
- **Credentials never leave the host.** Encrypted at rest, readable only by the owning connector (and principal, for personal auth), rendered by nothing.
|
|
85
87
|
- **Import-graph purity.** Nothing reachable from the root entry imports a `node:` builtin or `effect/testing`.
|
|
86
88
|
- **The published surface is a boundary.** Heavyweight or platform-bound code goes behind an optional-peer subpath; Effect is the one hard dependency, named by no published type.
|
|
87
|
-
- **Human routes manage auth, never capability.** Visibility grants use, not administration.
|
|
88
|
-
- **Omitted modules do no work.** Core imports no UI bundle, vault, activity, or bearer implementation. OAuth callbacks work without UI.
|
|
89
|
+
- **Human routes manage auth, never capability.** Visibility grants use, not administration. Auth mutations need separate config-derived permissions, denied by default; activity reads its own. Artifact viewing respects connector grants and cannot invoke tools.
|
|
90
|
+
- **Omitted modules do no work.** Core imports no UI bundle, vault, activity, artifacts, or bearer implementation. OAuth callbacks work without UI.
|
|
89
91
|
- **Status reads do not start authorization.** Only an explicit authorized action starts OAuth, never a page load.
|
|
90
92
|
- **Structural mistakes throw at construction.** Booting into the wrong shape is worse than not booting.
|
|
91
93
|
|