@zackbart/connecta 0.15.1 → 0.16.1

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 (335) hide show
  1. package/AGENTS.md +40 -20
  2. package/CHANGELOG.md +653 -0
  3. package/README.md +25 -5
  4. package/bin/connecta.mjs +44 -3
  5. package/dist/access-tokens.d.ts +0 -1
  6. package/dist/access-tokens.js +0 -1
  7. package/dist/activity.d.ts +50 -2
  8. package/dist/activity.js +34 -1
  9. package/dist/apps-shell.d.ts +0 -1
  10. package/dist/apps-shell.js +0 -1
  11. package/dist/auth/bearer.d.ts +0 -1
  12. package/dist/auth/bearer.js +0 -1
  13. package/dist/auth/clerk.d.ts +0 -1
  14. package/dist/auth/clerk.js +45 -9
  15. package/dist/auth/downstream-oauth.d.ts +0 -1
  16. package/dist/auth/downstream-oauth.js +0 -1
  17. package/dist/call-admission.d.ts +0 -1
  18. package/dist/call-admission.js +0 -1
  19. package/dist/catalog-drift.d.ts +99 -0
  20. package/dist/catalog-drift.js +251 -0
  21. package/dist/catalog-fingerprint.d.ts +0 -1
  22. package/dist/catalog-fingerprint.js +0 -1
  23. package/dist/catalog-limits.d.ts +0 -1
  24. package/dist/catalog-limits.js +0 -1
  25. package/dist/catalog-service.d.ts +0 -1
  26. package/dist/catalog-service.js +41 -5
  27. package/dist/catalog.d.ts +11 -1
  28. package/dist/catalog.js +20 -1
  29. package/dist/concurrency.d.ts +0 -1
  30. package/dist/concurrency.js +0 -1
  31. package/dist/connector-scope.d.ts +0 -1
  32. package/dist/connector-scope.js +0 -1
  33. package/dist/connectors/api.d.ts +29 -21
  34. package/dist/connectors/api.js +45 -19
  35. package/dist/connectors/guarded-fetch.d.ts +106 -0
  36. package/dist/connectors/guarded-fetch.js +289 -0
  37. package/dist/connectors/remote-mcp.d.ts +0 -1
  38. package/dist/connectors/remote-mcp.js +0 -1
  39. package/dist/credentials.d.ts +0 -1
  40. package/dist/credentials.js +0 -1
  41. package/dist/errors.d.ts +30 -3
  42. package/dist/errors.js +1 -1
  43. package/dist/execute.d.ts +0 -1
  44. package/dist/execute.js +0 -1
  45. package/dist/executor-admission.d.ts +11 -1
  46. package/dist/executor-admission.js +28 -1
  47. package/dist/executor-result.d.ts +0 -1
  48. package/dist/executor-result.js +0 -1
  49. package/dist/executors/quickjs-child.d.ts +0 -1
  50. package/dist/executors/quickjs-child.js +0 -1
  51. package/dist/executors/quickjs-protocol.d.ts +0 -1
  52. package/dist/executors/quickjs-protocol.js +0 -1
  53. package/dist/executors/quickjs-runtime.d.ts +0 -1
  54. package/dist/executors/quickjs-runtime.js +0 -1
  55. package/dist/executors/quickjs.d.ts +0 -1
  56. package/dist/executors/quickjs.js +2 -1
  57. package/dist/favicon.d.ts +0 -1
  58. package/dist/favicon.js +0 -1
  59. package/dist/index.d.ts +2 -3
  60. package/dist/index.js +24 -7
  61. package/dist/invocation.d.ts +0 -1
  62. package/dist/invocation.js +0 -1
  63. package/dist/json-schema.d.ts +0 -1
  64. package/dist/json-schema.js +0 -1
  65. package/dist/mcp-result.d.ts +0 -1
  66. package/dist/mcp-result.js +0 -1
  67. package/dist/meta-tools.d.ts +0 -1
  68. package/dist/meta-tools.js +0 -1
  69. package/dist/node.d.ts +0 -1
  70. package/dist/node.js +0 -1
  71. package/dist/operator-ui/generated.d.ts +2 -3
  72. package/dist/operator-ui/generated.js +3 -4
  73. package/dist/operator-ui/model.d.ts +10 -1
  74. package/dist/operator-ui/model.js +0 -1
  75. package/dist/operator-ui/view.d.ts +167 -0
  76. package/dist/operator-ui/view.js +270 -0
  77. package/dist/providers/cloudflare.d.ts +0 -1
  78. package/dist/providers/cloudflare.js +261 -299
  79. package/dist/providers/linear.d.ts +26 -5
  80. package/dist/providers/linear.js +32 -61
  81. package/dist/providers/mixpanel.d.ts +35 -4
  82. package/dist/providers/mixpanel.js +53 -79
  83. package/dist/providers/notion.d.ts +0 -1
  84. package/dist/providers/notion.js +87 -42
  85. package/dist/providers/stripe.d.ts +13 -1
  86. package/dist/providers/stripe.js +31 -60
  87. package/dist/registry.d.ts +34 -2
  88. package/dist/registry.js +85 -5
  89. package/dist/retired-toolkits.d.ts +0 -1
  90. package/dist/retired-toolkits.js +0 -1
  91. package/dist/routes/access-tokens.d.ts +0 -1
  92. package/dist/routes/access-tokens.js +0 -1
  93. package/dist/routes/activity.d.ts +0 -1
  94. package/dist/routes/activity.js +0 -1
  95. package/dist/routes/credentials.d.ts +0 -1
  96. package/dist/routes/credentials.js +0 -1
  97. package/dist/routes/mcp.d.ts +0 -1
  98. package/dist/routes/mcp.js +0 -1
  99. package/dist/routes/oauth.d.ts +0 -1
  100. package/dist/routes/oauth.js +0 -1
  101. package/dist/routes/shared.d.ts +2 -1
  102. package/dist/routes/shared.js +0 -1
  103. package/dist/routes/ui.d.ts +0 -1
  104. package/dist/routes/ui.js +0 -1
  105. package/dist/server.d.ts +0 -1
  106. package/dist/server.js +11 -1
  107. package/dist/skills.d.ts +0 -1
  108. package/dist/skills.js +0 -1
  109. package/dist/storage/file.d.ts +0 -1
  110. package/dist/storage/file.js +0 -1
  111. package/dist/storage/memory.d.ts +0 -1
  112. package/dist/storage/memory.js +0 -1
  113. package/dist/timeout.d.ts +0 -1
  114. package/dist/timeout.js +0 -1
  115. package/dist/tool-safety.d.ts +0 -1
  116. package/dist/tool-safety.js +0 -1
  117. package/dist/types.d.ts +45 -1
  118. package/dist/types.js +0 -1
  119. package/dist/ui.d.ts +0 -1
  120. package/dist/ui.js +13 -137
  121. package/dist/validate.d.ts +13 -15
  122. package/dist/validate.js +24 -11
  123. package/dist/version.d.ts +1 -2
  124. package/dist/version.js +1 -2
  125. package/documentation/architecture.md +186 -5
  126. package/documentation/auth.md +11 -0
  127. package/documentation/call-admission.md +142 -6
  128. package/documentation/cloudflare.md +92 -10
  129. package/documentation/code-first-exploration.md +2 -2
  130. package/documentation/code-mode.md +14 -14
  131. package/documentation/connector-guides.md +172 -5
  132. package/documentation/connectors.md +89 -0
  133. package/documentation/linear.md +20 -2
  134. package/documentation/mcp-ui-design.md +2 -2
  135. package/documentation/meta-tools.md +7 -1
  136. package/documentation/mixpanel.md +42 -9
  137. package/documentation/notion.md +23 -2
  138. package/documentation/operations.md +325 -5
  139. package/documentation/operator-ui.md +120 -5
  140. package/documentation/provider-audit.md +160 -0
  141. package/documentation/provider-conventions.md +667 -0
  142. package/documentation/request-admission.md +167 -5
  143. package/documentation/rich-output-design.md +1 -2
  144. package/documentation/stripe.md +15 -0
  145. package/documentation/upgrading.md +454 -0
  146. package/ethos.md +14 -9
  147. package/examples/worker/README.md +102 -12
  148. package/examples/worker/src/index.ts +46 -1
  149. package/examples/worker/wrangler.jsonc +14 -1
  150. package/package.json +10 -6
  151. package/templates/node/.dockerignore +10 -0
  152. package/templates/node/.env.example +40 -3
  153. package/templates/node/AGENTS.md +16 -1
  154. package/templates/node/Dockerfile +49 -0
  155. package/templates/node/README.md +117 -2
  156. package/templates/node/docker-compose.yml +39 -0
  157. package/templates/node/package.json +1 -1
  158. package/templates/node/src/file-activity.ts +182 -0
  159. package/templates/node/src/index.ts +80 -4
  160. package/assets/connecta-clay-hero.png +0 -0
  161. package/dist/access-tokens.d.ts.map +0 -1
  162. package/dist/access-tokens.js.map +0 -1
  163. package/dist/activity.d.ts.map +0 -1
  164. package/dist/activity.js.map +0 -1
  165. package/dist/apps-shell.d.ts.map +0 -1
  166. package/dist/apps-shell.js.map +0 -1
  167. package/dist/auth/bearer.d.ts.map +0 -1
  168. package/dist/auth/bearer.js.map +0 -1
  169. package/dist/auth/clerk.d.ts.map +0 -1
  170. package/dist/auth/clerk.js.map +0 -1
  171. package/dist/auth/downstream-oauth.d.ts.map +0 -1
  172. package/dist/auth/downstream-oauth.js.map +0 -1
  173. package/dist/call-admission.d.ts.map +0 -1
  174. package/dist/call-admission.js.map +0 -1
  175. package/dist/catalog-fingerprint.d.ts.map +0 -1
  176. package/dist/catalog-fingerprint.js.map +0 -1
  177. package/dist/catalog-limits.d.ts.map +0 -1
  178. package/dist/catalog-limits.js.map +0 -1
  179. package/dist/catalog-service.d.ts.map +0 -1
  180. package/dist/catalog-service.js.map +0 -1
  181. package/dist/catalog.d.ts.map +0 -1
  182. package/dist/catalog.js.map +0 -1
  183. package/dist/concurrency.d.ts.map +0 -1
  184. package/dist/concurrency.js.map +0 -1
  185. package/dist/connector-scope.d.ts.map +0 -1
  186. package/dist/connector-scope.js.map +0 -1
  187. package/dist/connectors/api.d.ts.map +0 -1
  188. package/dist/connectors/api.js.map +0 -1
  189. package/dist/connectors/remote-mcp.d.ts.map +0 -1
  190. package/dist/connectors/remote-mcp.js.map +0 -1
  191. package/dist/credentials.d.ts.map +0 -1
  192. package/dist/credentials.js.map +0 -1
  193. package/dist/errors.d.ts.map +0 -1
  194. package/dist/errors.js.map +0 -1
  195. package/dist/execute.d.ts.map +0 -1
  196. package/dist/execute.js.map +0 -1
  197. package/dist/executor-admission.d.ts.map +0 -1
  198. package/dist/executor-admission.js.map +0 -1
  199. package/dist/executor-result.d.ts.map +0 -1
  200. package/dist/executor-result.js.map +0 -1
  201. package/dist/executors/quickjs-child.d.ts.map +0 -1
  202. package/dist/executors/quickjs-child.js.map +0 -1
  203. package/dist/executors/quickjs-protocol.d.ts.map +0 -1
  204. package/dist/executors/quickjs-protocol.js.map +0 -1
  205. package/dist/executors/quickjs-runtime.d.ts.map +0 -1
  206. package/dist/executors/quickjs-runtime.js.map +0 -1
  207. package/dist/executors/quickjs.d.ts.map +0 -1
  208. package/dist/executors/quickjs.js.map +0 -1
  209. package/dist/favicon.d.ts.map +0 -1
  210. package/dist/favicon.js.map +0 -1
  211. package/dist/index.d.ts.map +0 -1
  212. package/dist/index.js.map +0 -1
  213. package/dist/invocation.d.ts.map +0 -1
  214. package/dist/invocation.js.map +0 -1
  215. package/dist/json-schema.d.ts.map +0 -1
  216. package/dist/json-schema.js.map +0 -1
  217. package/dist/mcp-result.d.ts.map +0 -1
  218. package/dist/mcp-result.js.map +0 -1
  219. package/dist/meta-tools.d.ts.map +0 -1
  220. package/dist/meta-tools.js.map +0 -1
  221. package/dist/node.d.ts.map +0 -1
  222. package/dist/node.js.map +0 -1
  223. package/dist/operator-ui/generated.d.ts.map +0 -1
  224. package/dist/operator-ui/generated.js.map +0 -1
  225. package/dist/operator-ui/model.d.ts.map +0 -1
  226. package/dist/operator-ui/model.js.map +0 -1
  227. package/dist/providers/cloudflare.d.ts.map +0 -1
  228. package/dist/providers/cloudflare.js.map +0 -1
  229. package/dist/providers/linear.d.ts.map +0 -1
  230. package/dist/providers/linear.js.map +0 -1
  231. package/dist/providers/mixpanel.d.ts.map +0 -1
  232. package/dist/providers/mixpanel.js.map +0 -1
  233. package/dist/providers/notion.d.ts.map +0 -1
  234. package/dist/providers/notion.js.map +0 -1
  235. package/dist/providers/stripe.d.ts.map +0 -1
  236. package/dist/providers/stripe.js.map +0 -1
  237. package/dist/registry.d.ts.map +0 -1
  238. package/dist/registry.js.map +0 -1
  239. package/dist/retired-toolkits.d.ts.map +0 -1
  240. package/dist/retired-toolkits.js.map +0 -1
  241. package/dist/routes/access-tokens.d.ts.map +0 -1
  242. package/dist/routes/access-tokens.js.map +0 -1
  243. package/dist/routes/activity.d.ts.map +0 -1
  244. package/dist/routes/activity.js.map +0 -1
  245. package/dist/routes/credentials.d.ts.map +0 -1
  246. package/dist/routes/credentials.js.map +0 -1
  247. package/dist/routes/mcp.d.ts.map +0 -1
  248. package/dist/routes/mcp.js.map +0 -1
  249. package/dist/routes/oauth.d.ts.map +0 -1
  250. package/dist/routes/oauth.js.map +0 -1
  251. package/dist/routes/shared.d.ts.map +0 -1
  252. package/dist/routes/shared.js.map +0 -1
  253. package/dist/routes/ui.d.ts.map +0 -1
  254. package/dist/routes/ui.js.map +0 -1
  255. package/dist/server.d.ts.map +0 -1
  256. package/dist/server.js.map +0 -1
  257. package/dist/skills.d.ts.map +0 -1
  258. package/dist/skills.js.map +0 -1
  259. package/dist/storage/file.d.ts.map +0 -1
  260. package/dist/storage/file.js.map +0 -1
  261. package/dist/storage/memory.d.ts.map +0 -1
  262. package/dist/storage/memory.js.map +0 -1
  263. package/dist/timeout.d.ts.map +0 -1
  264. package/dist/timeout.js.map +0 -1
  265. package/dist/tool-safety.d.ts.map +0 -1
  266. package/dist/tool-safety.js.map +0 -1
  267. package/dist/types.d.ts.map +0 -1
  268. package/dist/types.js.map +0 -1
  269. package/dist/ui.d.ts.map +0 -1
  270. package/dist/ui.js.map +0 -1
  271. package/dist/validate.d.ts.map +0 -1
  272. package/dist/validate.js.map +0 -1
  273. package/dist/version.d.ts.map +0 -1
  274. package/dist/version.js.map +0 -1
  275. package/examples/node/README.md +0 -52
  276. package/examples/node/src/index.ts +0 -71
  277. package/src/access-tokens.ts +0 -289
  278. package/src/activity.ts +0 -234
  279. package/src/apps-shell.ts +0 -370
  280. package/src/auth/bearer.ts +0 -61
  281. package/src/auth/clerk.ts +0 -553
  282. package/src/auth/downstream-oauth.ts +0 -575
  283. package/src/call-admission.ts +0 -521
  284. package/src/catalog-fingerprint.ts +0 -52
  285. package/src/catalog-limits.ts +0 -11
  286. package/src/catalog-service.ts +0 -1153
  287. package/src/catalog.ts +0 -790
  288. package/src/concurrency.ts +0 -39
  289. package/src/connector-scope.ts +0 -66
  290. package/src/connectors/api.ts +0 -159
  291. package/src/connectors/remote-mcp.ts +0 -1090
  292. package/src/credentials.ts +0 -428
  293. package/src/errors.ts +0 -364
  294. package/src/execute.ts +0 -1406
  295. package/src/executor-admission.ts +0 -328
  296. package/src/executor-result.ts +0 -107
  297. package/src/executors/quickjs-child.ts +0 -172
  298. package/src/executors/quickjs-protocol.ts +0 -99
  299. package/src/executors/quickjs-runtime.ts +0 -470
  300. package/src/executors/quickjs.ts +0 -776
  301. package/src/favicon.ts +0 -53
  302. package/src/index.ts +0 -666
  303. package/src/invocation.ts +0 -655
  304. package/src/json-schema.ts +0 -11
  305. package/src/mcp-result.ts +0 -43
  306. package/src/meta-tools.ts +0 -1607
  307. package/src/node.ts +0 -206
  308. package/src/operator-ui/browser.css +0 -423
  309. package/src/operator-ui/browser.ts +0 -1281
  310. package/src/operator-ui/generated.ts +0 -4
  311. package/src/operator-ui/model.ts +0 -111
  312. package/src/providers/cloudflare.ts +0 -3999
  313. package/src/providers/linear.ts +0 -302
  314. package/src/providers/mixpanel.ts +0 -229
  315. package/src/providers/notion.ts +0 -1879
  316. package/src/providers/stripe.ts +0 -307
  317. package/src/registry.ts +0 -1085
  318. package/src/retired-toolkits.ts +0 -14
  319. package/src/routes/access-tokens.ts +0 -115
  320. package/src/routes/activity.ts +0 -215
  321. package/src/routes/credentials.ts +0 -254
  322. package/src/routes/mcp.ts +0 -429
  323. package/src/routes/oauth.ts +0 -322
  324. package/src/routes/shared.ts +0 -247
  325. package/src/routes/ui.ts +0 -152
  326. package/src/server.ts +0 -195
  327. package/src/skills.ts +0 -271
  328. package/src/storage/file.ts +0 -123
  329. package/src/storage/memory.ts +0 -44
  330. package/src/timeout.ts +0 -84
  331. package/src/tool-safety.ts +0 -15
  332. package/src/types.ts +0 -523
  333. package/src/ui.ts +0 -744
  334. package/src/validate.ts +0 -387
  335. package/src/version.ts +0 -7
@@ -1,7 +1,169 @@
1
1
  # Request admission
2
2
 
3
- > **Stub.** The old manual was retired in the phase-1 docs restructure. This
4
- > document will be rewritten as an agent-facing guide what the subsystem is
5
- > for, how to work on it, and what it must never do — once the ideas in
6
- > [ethos.md](../ethos.md) settle. The prior text lives in git history as
7
- > `docs/request-admission.md`.
3
+ Connecta bounds work at the Web-standard request boundary before inbound
4
+ auth, before the MCP server exists, before any catalog is touched. A burst
5
+ therefore meets an explicit active count and an explicit queue instead of
6
+ asking traffic shape and the runtime allocator to pick the process high-water
7
+ mark.
8
+
9
+ There are two pools here and a third elsewhere. This guide covers the first
10
+ two: the deployment-wide `/mcp` pool and the fallback code pool. Per-connector
11
+ downstream bounds are [call admission](./call-admission.md), which is a
12
+ different question — request admission bounds the whole MCP envelope, call
13
+ admission bounds the individual `Connector.callTool` attempts fanned out inside
14
+ it.
15
+
16
+ ## The pools
17
+
18
+ Every non-preflight `/mcp` request takes one permit from a deployment-wide FIFO
19
+ pool. Initialization, discovery, ordinary calls, and `execute_code` all pay it.
20
+ A program then takes a *second* permit from the deliberately smaller code pool,
21
+ so one request cannot trade ordinary capacity for an unbounded number of
22
+ sandboxes.
23
+
24
+ ```ts
25
+ const connecta = createConnecta({
26
+ admission: {
27
+ requests: { concurrency: 16, maxQueueSize: 32, queueTimeoutMs: 5_000, retryAfterMs: 1_000 },
28
+ code: { concurrency: 2, maxQueueSize: 8, queueTimeoutMs: 5_000, retryAfterMs: 1_000 },
29
+ },
30
+ // …
31
+ });
32
+ ```
33
+
34
+ Those values are the defaults, and both pools are the same
35
+ `AdmissionController` (`src/executor-admission.ts`) with different numbers.
36
+ `maxQueueSize: 0` is the fail-fast shape. Every value is a finite whole number;
37
+ concurrency and the queue timeout must be positive, while queue size and the
38
+ retry hint may be zero. Invalid bounds throw at construction rather than
39
+ silently removing the deployment's memory boundary — a pool that quietly became
40
+ unbounded is worse than a deployment that refuses to boot.
41
+
42
+ `admission.code` is a *fallback*. An executor that implements `acquire()` is an
43
+ `AdmittingExecutor` and already owns a bounded pool, so its own settings win and
44
+ connecta warns that `admission.code` was ignored. `quickJsExecutor()` is one of
45
+ those: it defaults to one active execution and 32 queued callers, configured on
46
+ the executor rather than here. Cloudflare's `DynamicWorkerExecutor` is not, so
47
+ a Worker deployment gets the fallback pool wrapped around it at construction —
48
+ which is also why `/health` always has a code-admission shape to report.
49
+
50
+ The request pool is global FIFO across identities. It is a capacity boundary,
51
+ not tenant fairness: one busy caller can occupy it. Per-tenant fairness needs a
52
+ policy above connecta, and one deployment serves one audience anyway
53
+ ([`ethos.md`](../ethos.md)), so a global queue is not pretending to supply
54
+ something it does not.
55
+
56
+ ## Admission before auth
57
+
58
+ `/mcp` acquires its permit *before* running the auth gate. This looks backwards
59
+ until you price it: authenticating first means an unauthenticated flood buys a
60
+ Clerk network lookup per request, so the cheapest possible attack becomes the
61
+ most expensive request the server can serve. Admitting first means it buys a
62
+ queue slot and a 503.
63
+
64
+ The permit is released with the response *body*, not when the handler returns.
65
+ A slow client draining a large result still counts as active work, because its
66
+ bytes and its socket still exist. `releaseAdmissionWithResponse` re-wraps the
67
+ response stream to do this, absorbs a rejecting `cancel()` rather than leaking
68
+ an unhandled rejection, and releases exactly once — release is idempotent, and
69
+ `test/request-admission.test.ts` pins both the stream-cancel path and the
70
+ double-release case.
71
+
72
+ Every other route bypasses the pool entirely. `/health` and the operator
73
+ surface stay responsive while MCP is saturated, which is the whole point: an
74
+ operator diagnosing an overload must not have to queue behind it. `/health`
75
+ names the exempt routes in `admission.reservedRoutes` so the claim is checkable
76
+ from outside.
77
+
78
+ ## Overload, cancellation, shutdown
79
+
80
+ A full queue or an expired queue deadline answers HTTP 503 with `Retry-After`,
81
+ CORS headers, and a stable JSON-RPC error:
82
+
83
+ ```json
84
+ {
85
+ "jsonrpc": "2.0",
86
+ "id": null,
87
+ "error": {
88
+ "code": -32001,
89
+ "message": "Server capacity is exhausted. Retry later.",
90
+ "data": { "code": "server_overloaded", "retryable": true, "retryAfterMs": 1000 }
91
+ }
92
+ }
93
+ ```
94
+
95
+ `Retry-After` is that hint rounded up to at least one whole second. It is
96
+ advice, not a reservation. Shutdown uses `-32002` / `server_shutting_down` and
97
+ is not retryable. Code-pool overload never reaches this layer: it surfaces as
98
+ an ordinary MCP tool error with `executor_overloaded`, `retryable: true`, and
99
+ the executor's own `retryAfterMs`.
100
+
101
+ A cancelled queued request is removed immediately and never receives a later
102
+ permit — cancelling and then being admitted would hold capacity for a caller
103
+ that is gone. An admitted request keeps its permit until its body completes,
104
+ errors, or is cancelled.
105
+
106
+ `connecta.close()` closes both queues before releasing executor resources:
107
+ queued and future MCP work is rejected with `server_shutting_down` while
108
+ admitted work drains. Node's `listen()` calls it on SIGTERM or SIGINT, stops
109
+ accepting connections, drains, and enforces `shutdownTimeoutMs` (10 s default)
110
+ — SIGTERM arrives on every `docker compose up` recreate, and Node's default
111
+ response to it is to die mid-request.
112
+
113
+ ## What admission is not
114
+
115
+ The Node adapter's `maxBodyBytes` (10 MiB default) is a separate ingress guard.
116
+ It caps the HTTP body while constructing the Web `Request`, which happens
117
+ *before* the portable `/mcp` boundary can run. Admission bounds MCP, auth,
118
+ catalog, and response work; it is not a byte budget for many simultaneous slow
119
+ or near-limit uploads. Hostile public traffic wants an ingress proxy with a
120
+ body-rate limit in front, and `maxBodyBytes` set to the smallest value the
121
+ deployment actually needs.
122
+
123
+ The rejection warning is rate-limited to one per second, and each line reports
124
+ how many were suppressed since the last one. The `/health` totals count every
125
+ rejection, so the log is a sample and the counters are the record. Queue waits
126
+ log at debug level. Nothing on this path records or exposes request bodies,
127
+ tool arguments, identities, or results.
128
+
129
+ ## Observations
130
+
131
+ `/health` exposes payload-free snapshots under `admission.requests`,
132
+ `admission.code`, and `admission.downstreamCalls`. The first two carry
133
+ configured bounds, current active and queued counts, cumulative
134
+ admitted/queued/rejected/cancelled/closed totals, and queue-wait count, total,
135
+ and maximum. The request policy is labelled `global-fifo`; downstream policy is
136
+ labelled `connector-partitioned-per-runtime`. An executor that owns its own
137
+ pool and exposes no snapshot reports `{ managedByExecutor: true }`.
138
+
139
+ With `execute_code({ diagnostics: true })`, a caller sees the same split from
140
+ the inside: `admissionMs` is time spent waiting for a permit and `connectorMs`
141
+ is the admitted attempt.
142
+
143
+ ## Measuring capacity
144
+
145
+ `npm run load:admission` builds the package, then starts server and generator
146
+ in separate processes over real loopback TCP, warms a 10,000-tool catalog,
147
+ verifies every returned value, and records throughput, p50/p95/p99, server-only
148
+ peak RSS, RSS after forced GC, and live heap after GC. Each matrix cell gets a
149
+ fresh server so an earlier allocator high-water mark cannot contaminate the
150
+ next baseline; the three-round soak deliberately reuses one, because allocator
151
+ high-water retention and a live-object climb look identical in a single run and
152
+ different across three.
153
+
154
+ `CONNECTA_LOAD_CATALOG_SIZE`, `CONNECTA_LOAD_CONCURRENCY`, and
155
+ `CONNECTA_LOAD_MAX_QUEUE_SIZE` change catalog size, server concurrency, and
156
+ queue depth. The script prints its own numbers; no baseline is checked in,
157
+ deliberately. A laptop matrix is an example capacity profile, not a portable
158
+ SLO, and downstream payload size moves it more than any setting here does — pin
159
+ a runner before enforcing a regression ratio, and measure the connector mix the
160
+ deployment actually runs.
161
+
162
+ ## Tests that enforce this
163
+
164
+ | Invariant | Suite |
165
+ | --- | --- |
166
+ | FIFO bounds, active and queue ceilings, stable retryable overload, queue timeout, cancellation removal, idempotent release, shutdown | `test/executor-admission.test.ts` (Node + Workers) |
167
+ | `/mcp` bounded before auth, stable 503 and `Retry-After`, health and operator responsiveness under saturation, payload-free counters, queued cancellation, shutdown rejection while active work drains, the separate fallback code pool | `test/request-admission.test.ts` |
168
+ | A client disconnect propagating through the Web `Request` into a program's connector call, releasing both permits | `test/node.test.ts` |
169
+ | The `/health` admission payload alongside the drift counts | `test/catalog-drift.test.ts`, `test/server.test.ts` |
@@ -208,5 +208,4 @@ count of what was discarded (`M4`).
208
208
 
209
209
  The implementation issue carries these as acceptance criteria; the clauses
210
210
  fold into `code-mode.md` under a new "Emitted output" section when it closes,
211
- and the test-map row in [`operations.md`](./operations.md) lands with the
212
- suite.
211
+ and the suite takes its row in the [test map](./operations.md#the-test-map).
@@ -158,6 +158,14 @@ serves, an unclassified and unannotated `create_customer` lands on the approval
158
158
  path. Expect the undocumented Treasury tools Stripe alludes to to arrive
159
159
  unclassified as well — annotated ones will be taken at their word.
160
160
 
161
+ The upshot is that this account's tool list is not a fixed set, and the usage
162
+ guide tells the agent so: search this connector for what it actually exposes
163
+ rather than assuming a documented tool is present. The guide also names the id
164
+ discipline the downstream schemas cannot enforce — Stripe ids are typed
165
+ prefixes (`cus_`, `sub_`, `ch_`, `pi_`, `in_`, `acct_`), a plausible-looking one
166
+ belongs to a different object or to nobody, and the id a write takes comes from
167
+ `stripe_api_search` or a list read rather than from a guess.
168
+
161
169
  Stripe publishes no stability or deprecation policy for this tool set and
162
170
  invites tool requests by email, so treat the list as unversioned. `get_balance_summary`
163
171
  is Treasury, which Stripe labels public preview and gates behind an access
@@ -200,3 +208,10 @@ about rather than read:
200
208
  are Stripe's documented API conventions; how they thread through the generic
201
209
  tools' arguments is not documented. The guide states them because an agent
202
210
  that ignores them is wrong either way.
211
+
212
+ ## Conventions
213
+
214
+ This connection is audited against
215
+ [the provider conventions](./provider-conventions.md). Its verdict per
216
+ convention, including every recorded exception, is the Stripe section of
217
+ [the provider audit](./provider-audit.md).
@@ -0,0 +1,454 @@
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.16.1 | 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.16.1
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.16.1 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.16.1 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.0 → 0.16.1
211
+
212
+ Nothing throws, and a Node deployment crosses this on the version bump alone.
213
+ The one thing that can stop the upgrade is npm, and only on a Worker:
214
+ `@cloudflare/codemode` is now a declared optional peer at `^0.4.4 || ^0.5.0`,
215
+ so a `package.json` holding it below that range fails `npm install` with an
216
+ `ERESOLVE` conflict instead of installing. Move the pin into the range this
217
+ release is tested against. Separately, `cloudflare()` no longer names
218
+ `list_zone_settings` — read one setting with `get_zone_setting`, write one with
219
+ `update_zone_setting` — and Cloudflare's 404 now arrives as `not_found` rather
220
+ than `connector_call_failed`, which matters only to a program that branches on
221
+ the code.
222
+
223
+ ### 0.15.x → 0.16.0
224
+
225
+ The largest one, and it fails loudly. Every item here throws at construction or
226
+ fails a specific call; none of it degrades quietly.
227
+
228
+ **`api()` enforces its construction contract.** Every hand-written tool now
229
+ needs three things, checked when the connector is built:
230
+
231
+ - a non-empty `description` — it is what an agent reads to choose the tool;
232
+ - an explicit boolean `annotations.readOnlyHint` — `true` for a read, `false`
233
+ for work that must cross `call_destructive_tool`;
234
+ - an `inputSchema`, if it ships one, that the validator can actually compile.
235
+
236
+ The throw names the failing address (`connectorId.toolName`), so this is a
237
+ mechanical walk through `src/index.ts`. The one judgment call is the
238
+ classification, and the safe answer is written down for you: **a tool that used
239
+ to ship unannotated becomes `readOnlyHint: false`**, because unannotated
240
+ already routed through `call_destructive_tool`. That is the routing it had.
241
+ Writing `true` onto a tool you have not read is not an upgrade, it is a
242
+ capability change.
243
+
244
+ Connecta infers the classification from nothing — not a name, not a verb, not
245
+ an HTTP method, not the other annotations. There is no flag that restores the
246
+ old forgiving behavior.
247
+
248
+ **`ApiOptions.strictValidation` is gone.** Delete it. Fail-closed schema
249
+ handling is the only behavior left, so the option had nothing to switch. A
250
+ schema that only reveals itself as unenforceable on first use — an unresolvable
251
+ `$ref`, say — now fails that call as non-retryable `invalid_args` instead of
252
+ forwarding raw arguments to the handler. `validateArgs: false` still exists and
253
+ still means what it said: opting out of enforcement, not out of the schema
254
+ being real.
255
+
256
+ **`linear()` requires `access`.** Construction throws naming both options.
257
+ `access: "read-write"` preserves an existing deployment's behavior exactly;
258
+ `"read-only"` binds the connector to Linear's read-only endpoint, whose token
259
+ cannot reach the write APIs at all. There is no default because neither guess
260
+ is safe — one hands out writes nobody asked for, the other breaks a writing
261
+ deployment at Linear, at runtime, where no agent can repair it
262
+ ([linear](./linear.md)).
263
+
264
+ **`mixpanel()` declares no call-admission budget.** The old hardcoded 600
265
+ calls/hour transcribed a limit Mixpanel meters *per user*, which a per-runtime
266
+ counter cannot approximate in either direction. Nothing throws — the ceiling is
267
+ simply absent. If this deployment was relying on it, pass `callAdmission`
268
+ explicitly ([call admission](./call-admission.md)). The default title also now
269
+ carries the region (`Mixpanel (us)`), and an unknown `region` throws.
270
+
271
+ **Three Cloudflare tools are gone; the connection ships 52.** `set_r2_cors`,
272
+ `delete_r2_cors`, and `get_r2_metrics`. Grep the deployment — and any prompt,
273
+ skill, or runbook around it — for those names. No capability is lost:
274
+ `get_r2_cors` still reads a bucket's policy, CORS writes go through
275
+ `cloudflare_api_mutate` at
276
+ `PUT`/`DELETE /accounts/{accountId}/r2/buckets/{bucketName}/cors`, and metrics
277
+ through `cloudflare_api_get` at `/accounts/{accountId}/r2/metrics`
278
+ ([cloudflare](./cloudflare.md)).
279
+
280
+ **`cloudflare()` checks an overridden `baseUrl` at construction.** A
281
+ non-loopback plain-http origin, URL-embedded credentials, or a query or
282
+ fragment now throws where the option is written rather than on the first call.
283
+ A deployment pointing Cloudflare at an http proxy must move it to https or bind
284
+ it to loopback. The default base is unaffected, so a deployment that never set
285
+ `baseUrl` reads nothing here.
286
+
287
+ **Cloudflare and Notion refuse redirects and bound their reads.** Both now send
288
+ `redirect: "manual"`; a 3xx fails as non-retryable `connector_call_failed`
289
+ rather than re-sending the connector's credential to whatever origin `Location`
290
+ names. Both also cap the response they will read — 8 MiB for Cloudflare, 4 MiB
291
+ for Notion — checked against a declared `Content-Length` and again while the
292
+ body streams. Visible only to a deployment that was pulling something enormous
293
+ through a tool call, such as a `cloudflare_api_get` on a large R2 object. These
294
+ are ceilings on absurdity; anything near them was already past whatever
295
+ `maxResultBytes` the deployment set.
296
+
297
+ **The shipped defaults fail closed.** Covered under `.env.example` above, and
298
+ repeated here because it is the item most likely to be "fixed" backwards: an
299
+ empty `CONNECTA_TOKEN` that refuses to boot is the intended state of a fresh
300
+ `.env.example`, not a regression.
301
+
302
+ ### 0.13.x → 0.14.x
303
+
304
+ No API breaks. One behavior change worth knowing: vetted annotations on a
305
+ prebuilt connection no longer argue with an explicit downstream annotation in
306
+ either direction, so a name the downstream explicitly marks `readOnlyHint:
307
+ true` that no release has classified is now callable from `execute_code`
308
+ instead of failing closed onto the approval path. Silence on an unclassified
309
+ name still means not read-only. The one branch that still outranks the
310
+ downstream is a name a release reviewed and filed destructive.
311
+
312
+ ### 0.12.x → 0.13.0
313
+
314
+ No API breaks; text changed under existing deployments. Served tool
315
+ descriptions and the MCP `instructions` string were rewritten, and `skills({})`
316
+ now summarizes a connector guide from its first body line rather than its
317
+ heading — a guide opening `# Acme` that listed as "Acme" now lists as the
318
+ sentence beneath it. If this deployment's connectors carry usage guides, read
319
+ their first lines ([connector guides](./connector-guides.md)).
320
+
321
+ ### 0.10.x → 0.11.0
322
+
323
+ The executor boundary. **Every deployment must configure an executor** and
324
+ serves exactly seven tools; construction refuses to boot without one. On Node
325
+ that is `quickJsExecutor()` from `@zackbart/connecta/quickjs`; on Workers,
326
+ `new DynamicWorkerExecutor({ loader: env.LOADER })` from
327
+ `@cloudflare/codemode` plus its paid-plan Worker Loader binding.
328
+
329
+ The top-level `list_connectors`, `describe_tools`, and `batch_call`
330
+ registrations are gone. Their equivalents live inside `execute_code` as
331
+ `connecta.search`, `connecta.describe`, and `connecta.batch`
332
+ ([code mode](./code-mode.md)). Anything outside the deployment that called
333
+ those three by name — a client config, a prompt, a script — is what actually
334
+ breaks here; the deployment file itself only has to gain the executor and drop
335
+ `surface`.
336
+
337
+ ### 0.6.x → 0.7.0
338
+
339
+ Only a pre-template deployment is still down here; every generation A project
340
+ was born above this line. Three breaks, and the config one is in the table
341
+ below.
342
+
343
+ **A connector implementing `finishAuth` without `verifyState` can no longer
344
+ complete OAuth** (#62). The callback refuses with the same opaque 400 as every
345
+ other refusal, exchanges no code, and logs one operator-grade line naming the
346
+ connector and the missing hook. `verifyState` is optional in the type system and
347
+ required in practice wherever `finishAuth` is present, so nothing throws at
348
+ construction — the flow simply stops completing, which is the one item in this
349
+ guide you find by reading rather than by building. It reaches hand-written
350
+ connectors only: the shipped `remoteMcp` OAuth provider has always implemented
351
+ it. The old behavior was exchanging an authorization code with no CSRF guard at
352
+ all, so this is not a hook to stub out with `() => true`.
353
+
354
+ **`/`, `/credentials`, and `/activity` are core-owned routes** (#57). They
355
+ previously fell through to connector `handleRequest` and then to a 404, so a
356
+ connector that served any of the three is now shadowed without warning. `GET /`
357
+ returns the operator shell where 0.6.1 returned 404, and a non-GET on those
358
+ routes or on `/ui` returns 405 instead of falling through. Move such a handler
359
+ to a path the core does not own: `handleRequest` still runs for everything the
360
+ built-in routes miss, so it can add a route and never shadow one
361
+ ([architecture](./architecture.md)).
362
+
363
+ ### Removed options that throw
364
+
365
+ These fail at construction with their migration named rather than falling back
366
+ to a default, because silently ignoring a removed option is how a deployment
367
+ ends up running a policy its config file says it has:
368
+
369
+ | Option | Removed in | Do |
370
+ | --- | --- | --- |
371
+ | `toolkits`, `unscoped` | 0.8.1 (#178) | delete; deploy one instance per audience |
372
+ | `credentials.health`, `credentialHealth` | 0.8.1 (#179) | delete; credentials fail at use |
373
+ | `surface` | 0.11.0 (#273) | delete; there is one seven-tool surface |
374
+ | `calls.maxBatchResultBytes` | 0.11.0 (#273) | delete; program batching is bounded by `execute_code`'s own limits |
375
+ | flat v0.6 config paths | 0.7.0 | move into their groups — one complete migration error lists them ([operations](./operations.md#configuration)) |
376
+
377
+ ## Verify
378
+
379
+ In order, and do not skip the last one — the first three prove the package
380
+ works, not that this deployment does.
381
+
382
+ ```sh
383
+ npm run typecheck # ships with the template
384
+ CONNECTA_TOKEN=dev-token npm start # in one shell
385
+ CONNECTA_TOKEN=dev-token npm run doctor # in another
386
+ ```
387
+
388
+ `connecta doctor` is the gate. It asserts `/health` reports ok, that
389
+ `tools/list` is exactly the seven prescribed names — `authorize_connector`,
390
+ `call_destructive_tool`, `call_tool`, `execute_code`, `get_result`,
391
+ `search_tools`, `skills` — and that `execute_code` actually runs a program in
392
+ the sandbox. It names the executor the deployment reports rather than assuming
393
+ one: `QuickJS executed` on the Node template, `DynamicWorkerExecutor executed`
394
+ on a Worker. It also *reports* catalog drift without failing on it; drifted
395
+ counts here are a maintainer's next task, not a failed upgrade.
396
+
397
+ Then exercise this deployment's own connectors, which doctor knows nothing
398
+ about — it holds a bearer, and a client key does not get to learn a
399
+ deployment's configuration topology. One program covers discovery and a call:
400
+
401
+ ```js
402
+ // execute_code — an empty query browses the catalog
403
+ async () => {
404
+ const page = await connecta.search({ query: "", limit: 100 });
405
+ return {
406
+ total: page.total,
407
+ connectors: [...new Set(page.tools.map((t) => t.address.split(".")[0]))],
408
+ };
409
+ };
410
+ ```
411
+
412
+ Walk the list from the inventory you wrote down: every connector the owner
413
+ configured should appear, every credential slot should still be listed at
414
+ `/credentials`, and at least one real read per connector should return data.
415
+ A connector whose catalog is empty after an upgrade is usually a credential
416
+ that did not survive a state-file path change, not a broken release.
417
+
418
+ If the container half is now in play, `cp .env.example .env`, set
419
+ `CONNECTA_TOKEN`, and `docker compose up -d --build`, then point doctor at it.
420
+ Commit the `package-lock.json` that `npm install` wrote on the host — that is
421
+ what puts the image build on the reproducible `npm ci` path instead of
422
+ resolving the pin again inside a layer.
423
+
424
+ ## What not to do
425
+
426
+ Five refusals. Each one is somebody's plausible shortcut, and each one produces
427
+ a deployment that is quieter and wrong.
428
+
429
+ - **Do not re-init over the top.** `connecta init` refuses to merge into an
430
+ existing path and that refusal is load-bearing. Working around it — into a
431
+ scratch directory and then `cp -r` over the deployment, or by deleting the
432
+ project and regenerating — is how a connector set becomes a `time` connector.
433
+ Scratch directories are for reading and diffing, never for copying wholesale.
434
+ - **Do not weaken a fail-closed default to get green.** `readOnlyHint: true` on
435
+ a tool you have not read, a restored `CONNECTA_TOKEN=replace-me`,
436
+ `validateArgs: false` to silence a schema that will not compile, an
437
+ annotation "corrected" to match what the downstream claims: all of these turn
438
+ a construction error into a running deployment with a wider blast radius than
439
+ it had yesterday. The construction throw is the feature. Fix the input.
440
+ - **Do not pin back.** A deployment that boots on 0.15.1 and throws on 0.16.0
441
+ is a deployment telling you which line to fix, with the address in the error
442
+ message. Reverting the pin keeps the same defect and buries the report.
443
+ - **Do not copy Connecta internals into the deployment.** If something the
444
+ deployment needs is not exported, that is a package issue to file, not a file
445
+ to vendor. A deployment that carries a copy of a provider cannot be upgraded
446
+ by anyone, including you, next time.
447
+ - **Do not add a second project shape.** No alternate entrypoint, no second
448
+ container recipe, no parallel configuration path beside `src/index.ts`. There
449
+ are exactly two deployment shapes — [`templates/node/`](../templates/node/)
450
+ and the Worker example — and a third that is a diff away from one of them is
451
+ a shape this repository has already deleted once.
452
+
453
+ When something here disagrees with [`ethos.md`](../ethos.md), the ethos wins
454
+ and this guide is what needs fixing.