@zackbart/connecta 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/AGENTS.md +12 -5
  2. package/CHANGELOG.md +289 -0
  3. package/README.md +6 -1
  4. package/dist/catalog-service.d.ts +4 -0
  5. package/dist/catalog-service.js +49 -5
  6. package/dist/catalog.d.ts +11 -0
  7. package/dist/catalog.js +134 -12
  8. package/dist/errors.d.ts +28 -2
  9. package/dist/errors.js +1 -0
  10. package/dist/execute.d.ts +5 -0
  11. package/dist/execute.js +229 -161
  12. package/dist/invocation.js +3 -1
  13. package/dist/meta-tools.d.ts +4 -0
  14. package/dist/meta-tools.js +46 -14
  15. package/dist/operator-ui/generated.d.ts +1 -1
  16. package/dist/operator-ui/generated.js +1 -1
  17. package/dist/operator-ui/model.d.ts +3 -1
  18. package/dist/providers/cloudflare.js +13 -25
  19. package/dist/providers/mixpanel.d.ts +3 -5
  20. package/dist/providers/mixpanel.js +73 -5
  21. package/dist/providers/stripe.d.ts +2 -2
  22. package/dist/providers/stripe.js +13 -11
  23. package/dist/registry.d.ts +32 -9
  24. package/dist/registry.js +217 -33
  25. package/dist/routes/mcp.js +6 -0
  26. package/dist/skills.d.ts +4 -0
  27. package/dist/skills.js +157 -18
  28. package/dist/types.d.ts +14 -2
  29. package/dist/ui.js +4 -1
  30. package/dist/version.d.ts +1 -1
  31. package/dist/version.js +1 -1
  32. package/documentation/architecture.md +7 -4
  33. package/documentation/cloudflare.md +40 -8
  34. package/documentation/code-first-exploration.md +2 -2
  35. package/documentation/code-mode.md +45 -53
  36. package/documentation/connector-guides.md +24 -19
  37. package/documentation/connectors.md +13 -1
  38. package/documentation/meta-tools.md +33 -18
  39. package/documentation/mixpanel.md +20 -0
  40. package/documentation/notion.md +7 -2
  41. package/documentation/operations.md +74 -29
  42. package/documentation/operator-ui.md +12 -2
  43. package/documentation/provider-audit.md +4 -4
  44. package/documentation/provider-conventions.md +68 -19
  45. package/documentation/stripe.md +45 -14
  46. package/documentation/upgrading.md +478 -0
  47. package/ethos.md +4 -4
  48. package/examples/worker/README.md +13 -6
  49. package/package.json +7 -2
  50. package/templates/node/AGENTS.md +5 -0
  51. package/templates/node/package.json +1 -1
@@ -0,0 +1,478 @@
1
+ # Upgrading an existing deployment
2
+
3
+ For the agent working *inside* a generated deployment rather than inside this
4
+ repository. You did not create this project, you cannot re-run `connecta init`
5
+ over it, and the thing you are holding is two files' worth of the owner's
6
+ intent wrapped in eight files of template that has moved on without it.
7
+
8
+ The shape of the job is fixed by two facts. A deployment is
9
+ [config-as-code](../ethos.md): `src/index.ts` is the product, everything around
10
+ it is scaffolding. And `connecta init` **refuses to merge into an existing
11
+ path** — deliberately, because an initializer that overwrites is an initializer
12
+ that eventually overwrites a connector set. So an upgrade is not a re-init. It
13
+ is: read what you have, bump the pin, reconcile the scaffolding against the
14
+ current template with the deployment's own generation as the base, migrate what
15
+ the release notes broke, and prove it with `connecta doctor`.
16
+
17
+ Work on a branch. Every step below is reversible until you delete the old
18
+ lockfile, and you want the diff reviewable by whoever owns this deployment.
19
+
20
+ ## Read what you have first
21
+
22
+ Three questions, in order. Answer all three before editing anything — the
23
+ second and third are what stop you from "restoring" a file the owner changed on
24
+ purpose.
25
+
26
+ ### 1. What version is it pinned to
27
+
28
+ ```sh
29
+ node -p "require('./package.json').dependencies['@zackbart/connecta']"
30
+ ```
31
+
32
+ `init` writes that pin as an **exact** version, never a range, because the
33
+ generated deployment and the package are separate release units
34
+ ([operations](./operations.md#deployment-as-a-release-unit)). So the pin is
35
+ also a fact about history: it is the version of the CLI that generated this
36
+ project, unless someone has bumped it since — which `package-lock.json` will
37
+ tell you.
38
+
39
+ ```sh
40
+ node -p "require('./package-lock.json').packages['node_modules/@zackbart/connecta'].version"
41
+ ```
42
+
43
+ Pin and lockfile agreeing means nobody has touched the dependency by hand. They
44
+ disagreeing is the first thing to reconcile, and the lockfile is usually the
45
+ truth about what has actually been running.
46
+
47
+ A deployment with no `@zackbart/connecta` entry at all, or one carrying a range
48
+ (`^0.14.0`), was not produced by `connecta init` — treat it as the pre-template
49
+ case below.
50
+
51
+ ### 2. Which template generation it came from
52
+
53
+ The pin answers this, and the file layout corroborates it. Two generations
54
+ exist so far:
55
+
56
+ | Generation | Versions | Layout |
57
+ | --- | --- | --- |
58
+ | **pre-template** | before 0.10.2 | no `connecta init` existed; hand-written, or copied from the retired `examples/node` |
59
+ | **A** | 0.10.2 – 0.15.1 | `.env.example`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `README.md`, `package.json`, `src/index.ts`, `tsconfig.json` |
60
+ | **B** | 0.16.0 – 0.17.0 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
61
+
62
+ Generation A is a decade in template years and identifying it precisely does
63
+ not matter, because you are about to reconstruct it exactly rather than guess
64
+ at it. What matters is the boundary: a project with no `Dockerfile` is a
65
+ generation A project, and the whole container story is a change it has never
66
+ seen.
67
+
68
+ ### 3. What this deployment changed
69
+
70
+ Regenerate the deployment's *own* starting point and diff against it. `init`
71
+ pins from the CLI package's version, so an old CLI reproduces its own
72
+ generation byte for byte:
73
+
74
+ ```sh
75
+ SCRATCH=$(mktemp -d) # keep this shell
76
+ (cd "$SCRATCH" && npx @zackbart/connecta@0.15.1 init base) # ← the pin from step 1
77
+ ```
78
+
79
+ `$SCRATCH` is the one scratch path for the whole upgrade — the reconcile step
80
+ below generates the current template into it too, and every later command
81
+ resolves against it. Run the rest from the deployment root:
82
+
83
+ ```sh
84
+ diff -ru "$SCRATCH/base" . --exclude node_modules --exclude package-lock.json
85
+ ```
86
+
87
+ Everything that differs is deployment-owned and survives this upgrade
88
+ unconditionally. Everything identical is scaffolding you may replace without
89
+ asking. Expect the interesting half to be:
90
+
91
+ - **`src/index.ts`** — the connector set, `auth`, `storage`, `publicUrl`, and
92
+ whichever operator blocks were uncommented. This file is never overwritten,
93
+ only edited.
94
+ - **`package.json`** — extra dependencies (`@clerk/backend` if operator
95
+ sign-in is on, provider SDKs the owner added, anything a handler imports)
96
+ and extra scripts.
97
+ - **`.env.example` / deployment docs** — variables the owner's connectors read.
98
+ - Whole files that are not in any generation: extra `src/*.ts` modules behind
99
+ `api()` handlers, CI workflows, infrastructure.
100
+
101
+ Write the list down before you touch anything. You will use it twice: once to
102
+ know what to preserve, once to know what to re-verify at the end.
103
+
104
+ ## The upgrade
105
+
106
+ ### Bump the pin and install
107
+
108
+ ```sh
109
+ npm pkg set dependencies.@zackbart/connecta=0.17.0
110
+ npm install
111
+ ```
112
+
113
+ Exact, not a range. The reason `init` pins exactly applies just as much on the
114
+ way up: a deployment whose connector safety classifications can move under it
115
+ during an unrelated `npm install` is not reviewable.
116
+
117
+ `quickjs-emscripten` is a direct dependency of the generated project and an
118
+ optional peer of the package (`^0.32.0`), so npm will tell you if the pin the
119
+ template ships has fallen out of range. Match the template's pin rather than
120
+ inventing one. If operator sign-in is enabled, `@clerk/backend` (`^3.12.0`) is
121
+ the other optional peer — also a direct dependency of the deployment, because
122
+ optional peers never install with core.
123
+
124
+ Do not run the build yet. Reconcile first, so a single typecheck answers for
125
+ both the new package and the new scaffolding.
126
+
127
+ ### Reconcile the scaffolding
128
+
129
+ Generate the *current* template beside the base you already made, into the same
130
+ `$SCRATCH`:
131
+
132
+ ```sh
133
+ (cd "$SCRATCH" && npx @zackbart/connecta@0.17.0 init current)
134
+ ```
135
+
136
+ You now have a three-way merge with a real base: `$SCRATCH/base` is what this
137
+ deployment started as, `$SCRATCH/current` is what `init` produces today, and the
138
+ deployment is the third leg. For every file:
139
+
140
+ | base vs current | deployment vs base | Do |
141
+ | --- | --- | --- |
142
+ | unchanged | unchanged | nothing |
143
+ | unchanged | changed | keep the deployment's version |
144
+ | changed | unchanged | take `$SCRATCH/current`'s version |
145
+ | changed | changed | merge by hand — this is the only file class that needs judgment |
146
+
147
+ `diff3` or `git merge-file` will do the mechanical part. From the deployment
148
+ root, with the deployment's file first, the base second, and the current
149
+ template third:
150
+
151
+ ```sh
152
+ git merge-file -p src/index.ts "$SCRATCH/base/src/index.ts" \
153
+ "$SCRATCH/current/src/index.ts" > "$SCRATCH/merged-index.ts"
154
+ ```
155
+
156
+ New files in `$SCRATCH/current` that exist in neither base nor deployment are
157
+ pure additions — copy them in. For generation A that is the entire container story
158
+ (`Dockerfile`, `docker-compose.yml`, `.dockerignore`) plus
159
+ `src/file-activity.ts`.
160
+
161
+ Two things are worth knowing before you accept the merge:
162
+
163
+ - **`.env.example` is not decoration.** The 0.16.0 template ships
164
+ `CONNECTA_TOKEN=` **empty** on purpose: both Compose and `src/index.ts`
165
+ refuse to start until an operator sets it, where the old `replace-me` value
166
+ started a healthy, port-published deployment whose bearer token was a string
167
+ published in a public repository. Take the empty value. Adding the
168
+ deployment's own variables underneath is the merge; restoring a placeholder
169
+ bearer is not.
170
+ - **`src/index.ts` is a merge, not a takeover.** What you are adopting from
171
+ `$SCRATCH/current` is the environment reading (`PUBLIC_URL`, `CONNECTA_STATE_FILE`,
172
+ treating empty as unset — that is what lets one source serve both `npm start`
173
+ and the container) and the commented operator blocks. What you are keeping is
174
+ every connector, every credential slot, every handler, and every operator
175
+ block this deployment had already uncommented. If a block is live here and
176
+ commented in the current template, live wins.
177
+
178
+ `AGENTS.md` (and the `CLAUDE.md` symlink beside it) is the deployment's
179
+ instruction file for the next agent. Take the current one, then re-append
180
+ whatever the owner added — it is usually the only "template" file with real
181
+ local content in it.
182
+
183
+ ### The pre-template case
184
+
185
+ A deployment older than 0.10.2 has no base to diff against. Do not try to
186
+ manufacture one. Instead:
187
+
188
+ 1. `SCRATCH=$(mktemp -d)`, then
189
+ `(cd "$SCRATCH" && npx @zackbart/connecta@0.17.0 init current)` — there is no
190
+ `base` leg here, only the current template to read from.
191
+ 2. Copy `$SCRATCH/current` into the deployment file by file, **skipping
192
+ `src/index.ts`**.
193
+ 3. Port the deployment's existing configuration into the new `src/index.ts` by
194
+ hand, one connector at a time, reading each version boundary below as you
195
+ go.
196
+
197
+ It is more work and it is honest work: a project of that vintage predates the
198
+ executor requirement, the seven-tool surface, and the `api()` construction
199
+ contract, so it needs a read anyway.
200
+
201
+ ## Version boundaries
202
+
203
+ Only what breaks an existing deployment is listed. Everything else in
204
+ [`CHANGELOG.md`](../CHANGELOG.md) is additive, and a boundary absent from this
205
+ list is a boundary you can cross with a version bump. The sections run newest
206
+ first, so cross them bottom-up: start at the oldest one still above this
207
+ deployment's pin and work back up the page, because each boundary assumes the
208
+ older ones are already done.
209
+
210
+ ### 0.16.1 → 0.17.0
211
+
212
+ Two construction rules need a deployment check.
213
+
214
+ **A Dynamic Worker executor is loader-only.** The supported construction is
215
+ exactly:
216
+
217
+ ```ts
218
+ new DynamicWorkerExecutor({ loader: env.LOADER })
219
+ ```
220
+
221
+ Remove `bindings`, `modules`, or `globalOutbound` from that options object.
222
+ Those fields grant guest code ambient configuration, code, or egress. This is
223
+ the supported sandbox boundary rather than a new Connecta-side inspection of
224
+ the third-party executor object. The shipped Worker example was already
225
+ loader-only, and Node deployments use `quickJsExecutor()`, so neither needs a
226
+ change (#390).
227
+
228
+ **An explicit guide summary must fit discovery.** Connecta normalizes
229
+ whitespace in `usageGuide.summary` and refuses registry construction when the
230
+ result is longer than 120 characters. Shorten it to 120 characters or fewer,
231
+ or omit it and let Connecta derive a bounded summary from the guide's opening
232
+ prose. A blank explicit summary still takes the derived-summary path (#392).
233
+
234
+ ### 0.16.0 → 0.16.1
235
+
236
+ Nothing throws, and a Node deployment crosses this on the version bump alone.
237
+ The one thing that can stop the upgrade is npm, and only on a Worker:
238
+ `@cloudflare/codemode` is now a declared optional peer at `^0.4.4 || ^0.5.0`,
239
+ so a `package.json` holding it below that range fails `npm install` with an
240
+ `ERESOLVE` conflict instead of installing. Move the pin into the range this
241
+ release is tested against. Separately, `cloudflare()` no longer names
242
+ `list_zone_settings` — read one setting with `get_zone_setting`, write one with
243
+ `update_zone_setting` — and Cloudflare's 404 now arrives as `not_found` rather
244
+ than `connector_call_failed`, which matters only to a program that branches on
245
+ the code.
246
+
247
+ ### 0.15.x → 0.16.0
248
+
249
+ The largest one, and it fails loudly. Every item here throws at construction or
250
+ fails a specific call; none of it degrades quietly.
251
+
252
+ **`api()` enforces its construction contract.** Every hand-written tool now
253
+ needs three things, checked when the connector is built:
254
+
255
+ - a non-empty `description` — it is what an agent reads to choose the tool;
256
+ - an explicit boolean `annotations.readOnlyHint` — `true` for a read, `false`
257
+ for work that must cross `call_destructive_tool`;
258
+ - an `inputSchema`, if it ships one, that the validator can actually compile.
259
+
260
+ The throw names the failing address (`connectorId.toolName`), so this is a
261
+ mechanical walk through `src/index.ts`. The one judgment call is the
262
+ classification, and the safe answer is written down for you: **a tool that used
263
+ to ship unannotated becomes `readOnlyHint: false`**, because unannotated
264
+ already routed through `call_destructive_tool`. That is the routing it had.
265
+ Writing `true` onto a tool you have not read is not an upgrade, it is a
266
+ capability change.
267
+
268
+ Connecta infers the classification from nothing — not a name, not a verb, not
269
+ an HTTP method, not the other annotations. There is no flag that restores the
270
+ old forgiving behavior.
271
+
272
+ **`ApiOptions.strictValidation` is gone.** Delete it. Fail-closed schema
273
+ handling is the only behavior left, so the option had nothing to switch. A
274
+ schema that only reveals itself as unenforceable on first use — an unresolvable
275
+ `$ref`, say — now fails that call as non-retryable `invalid_args` instead of
276
+ forwarding raw arguments to the handler. `validateArgs: false` still exists and
277
+ still means what it said: opting out of enforcement, not out of the schema
278
+ being real.
279
+
280
+ **`linear()` requires `access`.** Construction throws naming both options.
281
+ `access: "read-write"` preserves an existing deployment's behavior exactly;
282
+ `"read-only"` binds the connector to Linear's read-only endpoint, whose token
283
+ cannot reach the write APIs at all. There is no default because neither guess
284
+ is safe — one hands out writes nobody asked for, the other breaks a writing
285
+ deployment at Linear, at runtime, where no agent can repair it
286
+ ([linear](./linear.md)).
287
+
288
+ **`mixpanel()` declares no call-admission budget.** The old hardcoded 600
289
+ calls/hour transcribed a limit Mixpanel meters *per user*, which a per-runtime
290
+ counter cannot approximate in either direction. Nothing throws — the ceiling is
291
+ simply absent. If this deployment was relying on it, pass `callAdmission`
292
+ explicitly ([call admission](./call-admission.md)). The default title also now
293
+ carries the region (`Mixpanel (us)`), and an unknown `region` throws.
294
+
295
+ **Three Cloudflare tools are gone; the connection ships 52.** `set_r2_cors`,
296
+ `delete_r2_cors`, and `get_r2_metrics`. Grep the deployment — and any prompt,
297
+ skill, or runbook around it — for those names. No capability is lost:
298
+ `get_r2_cors` still reads a bucket's policy, CORS writes go through
299
+ `cloudflare_api_mutate` at
300
+ `PUT`/`DELETE /accounts/{accountId}/r2/buckets/{bucketName}/cors`, and metrics
301
+ through `cloudflare_api_get` at `/accounts/{accountId}/r2/metrics`
302
+ ([cloudflare](./cloudflare.md)).
303
+
304
+ **`cloudflare()` checks an overridden `baseUrl` at construction.** A
305
+ non-loopback plain-http origin, URL-embedded credentials, or a query or
306
+ fragment now throws where the option is written rather than on the first call.
307
+ A deployment pointing Cloudflare at an http proxy must move it to https or bind
308
+ it to loopback. The default base is unaffected, so a deployment that never set
309
+ `baseUrl` reads nothing here.
310
+
311
+ **Cloudflare and Notion refuse redirects and bound their reads.** Both now send
312
+ `redirect: "manual"`; a 3xx fails as non-retryable `connector_call_failed`
313
+ rather than re-sending the connector's credential to whatever origin `Location`
314
+ names. Both also cap the response they will read — 8 MiB for Cloudflare, 4 MiB
315
+ for Notion — checked against a declared `Content-Length` and again while the
316
+ body streams. Visible only to a deployment that was pulling something enormous
317
+ through a tool call, such as a `cloudflare_api_get` on a large R2 object. These
318
+ are ceilings on absurdity; anything near them was already past whatever
319
+ `maxResultBytes` the deployment set.
320
+
321
+ **The shipped defaults fail closed.** Covered under `.env.example` above, and
322
+ repeated here because it is the item most likely to be "fixed" backwards: an
323
+ empty `CONNECTA_TOKEN` that refuses to boot is the intended state of a fresh
324
+ `.env.example`, not a regression.
325
+
326
+ ### 0.13.x → 0.14.x
327
+
328
+ No API breaks. One behavior change worth knowing: vetted annotations on a
329
+ prebuilt connection no longer argue with an explicit downstream annotation in
330
+ either direction, so a name the downstream explicitly marks `readOnlyHint:
331
+ true` that no release has classified is now callable from `execute_code`
332
+ instead of failing closed onto the approval path. Silence on an unclassified
333
+ name still means not read-only. The one branch that still outranks the
334
+ downstream is a name a release reviewed and filed destructive.
335
+
336
+ ### 0.12.x → 0.13.0
337
+
338
+ No API breaks; text changed under existing deployments. Served tool
339
+ descriptions and the MCP `instructions` string were rewritten, and `skills({})`
340
+ now summarizes a connector guide from its first body line rather than its
341
+ heading — a guide opening `# Acme` that listed as "Acme" now lists as the
342
+ sentence beneath it. If this deployment's connectors carry usage guides, read
343
+ their first lines ([connector guides](./connector-guides.md)).
344
+
345
+ ### 0.10.x → 0.11.0
346
+
347
+ The executor boundary. **Every deployment must configure an executor** and
348
+ serves exactly seven tools; construction refuses to boot without one. On Node
349
+ that is `quickJsExecutor()` from `@zackbart/connecta/quickjs`; on Workers,
350
+ `new DynamicWorkerExecutor({ loader: env.LOADER })` from
351
+ `@cloudflare/codemode` plus its paid-plan Worker Loader binding.
352
+
353
+ The top-level `list_connectors`, `describe_tools`, and `batch_call`
354
+ registrations are gone. Their equivalents live inside `execute_code` as
355
+ `connecta.search`, `connecta.describe`, and `connecta.batch`
356
+ ([code mode](./code-mode.md)). Anything outside the deployment that called
357
+ those three by name — a client config, a prompt, a script — is what actually
358
+ breaks here; the deployment file itself only has to gain the executor and drop
359
+ `surface`.
360
+
361
+ ### 0.6.x → 0.7.0
362
+
363
+ Only a pre-template deployment is still down here; every generation A project
364
+ was born above this line. Three breaks, and the config one is in the table
365
+ below.
366
+
367
+ **A connector implementing `finishAuth` without `verifyState` can no longer
368
+ complete OAuth** (#62). The callback refuses with the same opaque 400 as every
369
+ other refusal, exchanges no code, and logs one operator-grade line naming the
370
+ connector and the missing hook. `verifyState` is optional in the type system and
371
+ required in practice wherever `finishAuth` is present, so nothing throws at
372
+ construction — the flow simply stops completing, which is the one item in this
373
+ guide you find by reading rather than by building. It reaches hand-written
374
+ connectors only: the shipped `remoteMcp` OAuth provider has always implemented
375
+ it. The old behavior was exchanging an authorization code with no CSRF guard at
376
+ all, so this is not a hook to stub out with `() => true`.
377
+
378
+ **`/`, `/credentials`, and `/activity` are core-owned routes** (#57). They
379
+ previously fell through to connector `handleRequest` and then to a 404, so a
380
+ connector that served any of the three is now shadowed without warning. `GET /`
381
+ returns the operator shell where 0.6.1 returned 404, and a non-GET on those
382
+ routes or on `/ui` returns 405 instead of falling through. Move such a handler
383
+ to a path the core does not own: `handleRequest` still runs for everything the
384
+ built-in routes miss, so it can add a route and never shadow one
385
+ ([architecture](./architecture.md)).
386
+
387
+ ### Removed options that throw
388
+
389
+ These fail at construction with their migration named rather than falling back
390
+ to a default, because silently ignoring a removed option is how a deployment
391
+ ends up running a policy its config file says it has:
392
+
393
+ | Option | Removed in | Do |
394
+ | --- | --- | --- |
395
+ | `toolkits`, `unscoped` | 0.8.1 (#178) | delete; deploy one instance per audience |
396
+ | `credentials.health`, `credentialHealth` | 0.8.1 (#179) | delete; credentials fail at use |
397
+ | `surface` | 0.11.0 (#273) | delete; there is one seven-tool surface |
398
+ | `calls.maxBatchResultBytes` | 0.11.0 (#273) | delete; program batching is bounded by `execute_code`'s own limits |
399
+ | flat v0.6 config paths | 0.7.0 | move into their groups — one complete migration error lists them ([operations](./operations.md#configuration)) |
400
+
401
+ ## Verify
402
+
403
+ In order, and do not skip the last one — the first three prove the package
404
+ works, not that this deployment does.
405
+
406
+ ```sh
407
+ npm run typecheck # ships with the template
408
+ CONNECTA_TOKEN=dev-token npm start # in one shell
409
+ CONNECTA_TOKEN=dev-token npm run doctor # in another
410
+ ```
411
+
412
+ `connecta doctor` is the gate. It asserts `/health` reports ok, that
413
+ `tools/list` is exactly the seven prescribed names — `authorize_connector`,
414
+ `call_destructive_tool`, `call_tool`, `execute_code`, `get_result`,
415
+ `search_tools`, `skills` — and that `execute_code` actually runs a program in
416
+ the sandbox. It names the executor the deployment reports rather than assuming
417
+ one: `QuickJS executed` on the Node template, `DynamicWorkerExecutor executed`
418
+ on a Worker. It also *reports* catalog drift without failing on it; drifted
419
+ counts here are a maintainer's next task, not a failed upgrade.
420
+
421
+ Then exercise this deployment's own connectors, which doctor knows nothing
422
+ about — it holds a bearer, and a client key does not get to learn a
423
+ deployment's configuration topology. One program covers discovery and a call:
424
+
425
+ ```js
426
+ // execute_code — an empty query browses the catalog
427
+ async () => {
428
+ const page = await connecta.search({ query: "", limit: 100 });
429
+ return {
430
+ total: page.total,
431
+ connectors: [...new Set(page.tools.map((t) => t.address.split(".")[0]))],
432
+ };
433
+ };
434
+ ```
435
+
436
+ Walk the list from the inventory you wrote down: every connector the owner
437
+ configured should appear, every credential slot should still be listed at
438
+ `/credentials`, and at least one real read per connector should return data.
439
+ A connector whose catalog is empty after an upgrade is usually a credential
440
+ that did not survive a state-file path change, not a broken release.
441
+
442
+ If the container half is now in play, `cp .env.example .env`, set
443
+ `CONNECTA_TOKEN`, and `docker compose up -d --build`, then point doctor at it.
444
+ Commit the `package-lock.json` that `npm install` wrote on the host — that is
445
+ what puts the image build on the reproducible `npm ci` path instead of
446
+ resolving the pin again inside a layer.
447
+
448
+ ## What not to do
449
+
450
+ Five refusals. Each one is somebody's plausible shortcut, and each one produces
451
+ a deployment that is quieter and wrong.
452
+
453
+ - **Do not re-init over the top.** `connecta init` refuses to merge into an
454
+ existing path and that refusal is load-bearing. Working around it — into a
455
+ scratch directory and then `cp -r` over the deployment, or by deleting the
456
+ project and regenerating — is how a connector set becomes a `time` connector.
457
+ Scratch directories are for reading and diffing, never for copying wholesale.
458
+ - **Do not weaken a fail-closed default to get green.** `readOnlyHint: true` on
459
+ a tool you have not read, a restored `CONNECTA_TOKEN=replace-me`,
460
+ `validateArgs: false` to silence a schema that will not compile, an
461
+ annotation "corrected" to match what the downstream claims: all of these turn
462
+ a construction error into a running deployment with a wider blast radius than
463
+ it had yesterday. The construction throw is the feature. Fix the input.
464
+ - **Do not pin back.** A deployment that boots on 0.15.1 and throws on 0.16.0
465
+ is a deployment telling you which line to fix, with the address in the error
466
+ message. Reverting the pin keeps the same defect and buries the report.
467
+ - **Do not copy Connecta internals into the deployment.** If something the
468
+ deployment needs is not exported, that is a package issue to file, not a file
469
+ to vendor. A deployment that carries a copy of a provider cannot be upgraded
470
+ by anyone, including you, next time.
471
+ - **Do not add a second project shape.** No alternate entrypoint, no second
472
+ container recipe, no parallel configuration path beside `src/index.ts`. There
473
+ are exactly two deployment shapes — [`templates/node/`](../templates/node/)
474
+ and the Worker example — and a third that is a diff away from one of them is
475
+ a shape this repository has already deleted once.
476
+
477
+ When something here disagrees with [`ethos.md`](../ethos.md), the ethos wins
478
+ and this guide is what needs fixing.
package/ethos.md CHANGED
@@ -49,8 +49,8 @@ order, and amending it is a design decision, not a drive-by edit.
49
49
  changes declared capability, no policy engine, no approvals, no pauses.
50
50
  - **Not a schema ingester.** No OpenAPI or GraphQL → tools. Generated tool
51
51
  sprawl is the disease the meta-tools treat, not a feature to add.
52
- - **Not multi-tenant.** No accounts dimension, no per-user credential store,
53
- no org hierarchy. Two accounts on one service are two connector instances.
52
+ - **Not multi-tenant.** No Connecta account model, per-user credential store, or org hierarchy; credentials, storage, admission, and health stay connector-scoped.
53
+ Provider-owned sessions may expose provider-native account scope only through live schemas. Metadata never proves account identity; Connecta never invents or normalizes selectors, and ambiguity stops.
54
54
  - **Not stateful.** No protocol sessions, no server push. Scope resolves per
55
55
  request — which is also where the MCP spec itself has now arrived.
56
56
  - **Not a nanny.** Credentials fail loudly at use; connecta never probes one.
@@ -84,7 +84,7 @@ proposing one without a new argument is not.
84
84
  | Operator boundary reworded: authentication material, never declared capability | accepted | supersedes "observable, never administrable", which had stopped describing the surface: operator routes already rotate credentials, issue and revoke access tokens, and drive downstream OAuth, each under its own accepted row, and the owner has decided the surface stays actionable — so the boundary now says what is actually true, that operator routes may manage authentication material for capabilities declared in deployment configuration and may not change the connector set, the declared tool catalog or annotations, requested OAuth scopes, admission policy, authorization rules, or caller tool scope; the claim is deliberately about *declared* capability, and twice so, because replacing an API token with a broader-scoped one widens downstream reach and no browser page can honestly promise otherwise, and because a remote MCP server's catalog is discovered rather than declared — connecta declares the connector, its credential slot, and its admission policy, while the tools that server serves are its own answer, so storing a credential or finishing an OAuth flow can legitimately take an `mcp()` connector from no tools to N, which is discovery arriving, not an operator editing the deployment ([#338](https://github.com/zackbart/connecta/issues/338)) |
85
85
  | Structured result surface | accepted | canonical `structuredContent` plus complete compact `content`; summary-only text is gated on host-forwarding evidence ([#191](https://github.com/zackbart/connecta/issues/191)) |
86
86
  | Code mode (`execute_code`) | accepted | the primary read, discovery, and composition surface: smaller serialized definitions, far smaller results once composition and projection happen before the model sees them, and a cold-start model that read the interface without help ([exploration](./documentation/code-first-exploration.md), [#224](https://github.com/zackbart/connecta/issues/224)) |
87
- | Code-first as the default; the eval gate retired | accepted | owner decision, 2026-07-30: one operator, no deploy-time flip; [`eval/code-first-gate`](./eval/code-first-gate/README.md) survives as measurement, but nothing waits on its verdict ([#222](https://github.com/zackbart/connecta/issues/222), [#224](https://github.com/zackbart/connecta/issues/224)) |
87
+ | Code-first as the default; the eval gate retired | accepted | owner decision, 2026-07-30: one operator, no deploy-time flip; [`eval/code-first-gate`](https://github.com/zackbart/connecta/blob/main/eval/code-first-gate/README.md) survives as measurement, but nothing waits on its verdict ([#222](https://github.com/zackbart/connecta/issues/222), [#224](https://github.com/zackbart/connecta/issues/224)) |
88
88
  | Surface consolidation to seven tools | accepted | `list_connectors`, `describe_tools`, and `batch_call` fold into the program surface, deleting the routing choice between direct calls, batches, discovery, and execution; `call_tool` stays because a simple call is not cheaper through code ([#224](https://github.com/zackbart/connecta/issues/224)) |
89
89
  | Classic (executor-free) surface | removed | supersedes its provisional retention under [#224](https://github.com/zackbart/connecta/issues/224) — an executor is mandatory, and a deployment without one refuses to boot rather than serving a fallback shape ([#273](https://github.com/zackbart/connecta/issues/273)) |
90
90
  | Connector shortcut namespaces in programs | accepted | sugar over canonical addressing, kept but frozen — every expansion invents a collision class `<connectorId>.<toolName>` already solved ([#223](https://github.com/zackbart/connecta/issues/223)) |
@@ -97,6 +97,7 @@ proposing one without a new argument is not.
97
97
  | MRTR / `input_required` passthrough | gated | statelessly relayable via `requestState`, but no host or downstream emits it yet; fails loudly until adoption evidence ([#176](https://github.com/zackbart/connecta/issues/176)) |
98
98
  | Native Tasks for oversized results | refused | tasks solve duration, `get_result` solves size; paging on a polling extension adds round trips for nothing ([#176](https://github.com/zackbart/connecta/issues/176)) |
99
99
  | Downstream `ttlMs` cache hints | gated | fixed TTL + fingerprint is battle-tested and catalog reads are ~3 ms; earns its way in with refresh-churn evidence ([#176](https://github.com/zackbart/connecta/issues/176)) |
100
+ | Inbound-demand catalog stale-while-revalidate | accepted | an agent-facing catalog read may return a complete, fingerprint-verified entry inside its configured stale window and move the refresh that same read demanded onto the runtime's existing deferred-work channel; this originates no timer, schedule, warmup, or credential probe, and operator status remains blocking — one refresh per connector is shared across agent and operator requests, a deferred owner uses a fresh bounded signal and connector scope and closes that scope, and invalidation is rechecked before stale publication, so no request-bound capability survives and no partial or pre-invalidation catalog gains authority ([#396](https://github.com/zackbart/connecta/issues/396)) |
100
101
  | Rich program output (`connecta.emit`) | accepted | one host-collected emission channel: programs emit strictly validated text/image/audio blocks, delivered after the result envelope on success only; budgets fail loudly at the emit call ([design record](./documentation/rich-output-design.md), [#267](https://github.com/zackbart/connecta/issues/267)) |
101
102
  | Result-channel widening of the `Executor` contract | refused | `ExecuteResult` stays `{ result, error?, logs? }` — structural compatibility with `@cloudflare/codemode` is the parity guarantee; emission rides the provider bridge instead ([#267](https://github.com/zackbart/connecta/issues/267)) |
102
103
  | Guest-emitted `resource` / `resource_link` blocks | refused | a program can never mint a URI a client may dereference, and the carve-out this row waited on does not need one: `connecta.ui` takes HTML content, and the only `ui://` URI is connecta's build-time shell ([design record](./documentation/mcp-ui-design.md), [#266](https://github.com/zackbart/connecta/issues/266), [#267](https://github.com/zackbart/connecta/issues/267)) |
@@ -108,7 +109,6 @@ proposing one without a new argument is not.
108
109
  | View-initiated mutation calls from program UI | gated | live-read utility says nothing about write consent: a click is not approval, stale/replayed effects need a host-tested story, and the ordinary destructive path keeps the action in the transcript ([#287](https://github.com/zackbart/connecta/issues/287)) |
109
110
  | Result sampling on the catalog surface (`sample` / `dryRun`) | refused | sampling is execution and cannot ride a catalog read; most tools carry required arguments no sampler can invent, and undeclared `outputSchema` (measured 0/30 and 3/30 on real deployments) is a real gap that is not a sampleable one — a program that checks the shape before rendering already hands back the first record inside the run it was going to make anyway, at zero new surface ([#282](https://github.com/zackbart/connecta/issues/282)) |
110
111
  | Legacy embedded `UIResource` delivery | refused | superseded upstream and rendered by none of the clients connecta faces; per-request minted URIs also fight the caching the Apps spec assumes ([#266](https://github.com/zackbart/connecta/issues/266)) |
111
-
112
112
  ## Invariants
113
113
 
114
114
  One line each; the enforcing tests live beside the subsystem documentation.
@@ -60,9 +60,15 @@ things, because two of them are not part of connecta and never install with it:
60
60
  npm install @zackbart/connecta @cloudflare/codemode @clerk/backend
61
61
  ```
62
62
 
63
- `@cloudflare/codemode` is the executor behind `execute_code`, and
64
- `@clerk/backend` is the optional peer behind `@zackbart/connecta/auth/clerk` —
65
- which `src/index.ts` imports at the top level, so wrangler must resolve it at
63
+ Both are optional peers of `@zackbart/connecta` declared in its manifest,
64
+ never installed with it, and each carrying the range this release supports.
65
+ `@cloudflare/codemode` is the executor behind `execute_code`, published as
66
+ `^0.4.4 || ^0.5.0`: install a version inside that and npm stays quiet, install
67
+ one outside and npm says so at install time instead of leaving a Worker to
68
+ discover the skew in production ([#376](https://github.com/zackbart/connecta/issues/376)).
69
+
70
+ `@clerk/backend` is the peer behind `@zackbart/connecta/auth/clerk`, which
71
+ `src/index.ts` imports at the top level, so wrangler must resolve it at
66
72
  build time. Miss it and the build stops at
67
73
  `Could not resolve "@clerk/backend"`, which is a missing peer rather than a
68
74
  broken example. Drop `clerkAuth` from `auth` if this deployment has no operator
@@ -146,9 +152,10 @@ The required Worker Loader binding is checked into `wrangler.jsonc`:
146
152
  "worker_loaders": [{ "binding": "LOADER" }]
147
153
  ```
148
154
 
149
- `src/index.ts` constructs `DynamicWorkerExecutor` from `env.LOADER` and serves
150
- the seven-tool surface. The executor package is one of the installs a copied
151
- deployment owns see
155
+ `src/index.ts` constructs `DynamicWorkerExecutor` with only `env.LOADER` and
156
+ serves the seven-tool surface. Do not add `bindings`, `modules`, or
157
+ `globalOutbound`; they grant guest code ambient authority. A copied deployment
158
+ owns the package install — see
152
159
  [copied into its own repository](#copied-into-its-own-repository).
153
160
 
154
161
  ## Activity history (optional)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "One MCP to rule them all — a single MCP endpoint aggregating many downstream connectors behind a code-first surface of seven meta-tools.",
@@ -46,6 +46,7 @@
46
46
  "types": "./dist/index.d.ts",
47
47
  "import": "./dist/index.js"
48
48
  },
49
+ "./package.json": "./package.json",
49
50
  "./node": {
50
51
  "types": "./dist/node.d.ts",
51
52
  "import": "./dist/node.js"
@@ -115,19 +116,23 @@
115
116
  },
116
117
  "peerDependencies": {
117
118
  "@clerk/backend": "^3.12.0",
119
+ "@cloudflare/codemode": "^0.4.4 || ^0.5.0",
118
120
  "quickjs-emscripten": "^0.32.0"
119
121
  },
120
122
  "peerDependenciesMeta": {
121
123
  "@clerk/backend": {
122
124
  "optional": true
123
125
  },
126
+ "@cloudflare/codemode": {
127
+ "optional": true
128
+ },
124
129
  "quickjs-emscripten": {
125
130
  "optional": true
126
131
  }
127
132
  },
128
133
  "devDependencies": {
129
134
  "@clerk/backend": "^3.12.0",
130
- "@cloudflare/codemode": "^0.4.4",
135
+ "@cloudflare/codemode": "^0.5.0",
131
136
  "@cloudflare/vitest-pool-workers": "^0.18.8",
132
137
  "@cloudflare/workers-types": "^4.20250101.0 || ^5.0.0",
133
138
  "@playwright/test": "^1.62.0",
@@ -23,6 +23,11 @@ This repository is deployment configuration, not a copy of Connecta itself.
23
23
  - `Dockerfile` and `docker-compose.yml` containerize *this* source; they are
24
24
  the same deployment, not a second one. Configuration belongs in `.env` and
25
25
  `src/index.ts`, never in a divergent container entrypoint.
26
+ - Moving this deployment to a newer Connecta is its own procedure, and it is
27
+ not a re-`init` — `connecta init` refuses to merge into an existing path on
28
+ purpose. Follow
29
+ [the upgrade guide](https://github.com/zackbart/connecta/blob/main/documentation/upgrading.md),
30
+ which also ships at `node_modules/@zackbart/connecta/documentation/upgrading.md`.
26
31
 
27
32
  Do not add alternate entrypoints, policy layers, generated connector catalogs,
28
33
  or runtime connector registration. Keep the deployment small enough to review
@@ -12,7 +12,7 @@
12
12
  "typecheck": "tsc --noEmit"
13
13
  },
14
14
  "dependencies": {
15
- "@zackbart/connecta": "0.16.0",
15
+ "@zackbart/connecta": "0.17.0",
16
16
  "quickjs-emscripten": "0.32.0"
17
17
  },
18
18
  "devDependencies": {