@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
@@ -0,0 +1,667 @@
1
+ # Provider conventions
2
+
3
+ The five maintained prebuilt connections grew one at a time, and until now
4
+ "excellent provider" meant whatever the last author thought. This document
5
+ writes the judgment down so it can be argued with, audited, and reused.
6
+
7
+ There are two genuinely different provider shapes, and one convention set
8
+ cannot honestly cover both:
9
+
10
+ - **Hand-written HTTP providers** — `api()` surfaces where Connecta owns every
11
+ tool name, schema, projection, and error. Today: Cloudflare, Notion.
12
+ - **Hosted-MCP proxies** — `remoteMcp()` wrappers around a server somebody else
13
+ operates, where the names, schemas, results, and error prose arrive as they
14
+ are. Today: Linear, Stripe, Mixpanel.
15
+
16
+ The governing principle for every convention below is the same: **keep the
17
+ model that interacts with connecta as efficient as possible.** A convention
18
+ earns its place by reducing what an agent spends discovering, selecting,
19
+ calling, and reading. It does not earn its place by being tidy.
20
+
21
+ Nothing here overrides [ethos.md](../ethos.md). In particular, prebuilt
22
+ connections remain ordinary `Connector` instances with no extra privileges,
23
+ their annotations fill in downstream silence rather than replacing it, and no
24
+ tool is ever generated from a schema document.
25
+
26
+ ## How to read a convention
27
+
28
+ Every convention is one rule, one reason, and one cost. The cost is drawn from
29
+ a fixed vocabulary of four, because those are the four things an agent actually
30
+ pays:
31
+
32
+ | Cost | What it means |
33
+ | --- | --- |
34
+ | **discovery tokens** | bytes the agent reads before it can call anything |
35
+ | **wrong-tool selection** | it picked the wrong tool, connector, or mode |
36
+ | **argument retries** | the call was made and rejected, so it must be made again |
37
+ | **result size** | bytes the agent reads back, and the round trips paging them |
38
+
39
+ A convention that cannot name one of those four is a preference, not a
40
+ convention, and does not belong in this document.
41
+
42
+ ## What discovery actually shows
43
+
44
+ Several conventions are budgets, and the budgets are not arbitrary — they are
45
+ the points at which Connecta's own surface starts dropping characters on the
46
+ floor. From `src/catalog.ts` and `src/catalog-service.ts`:
47
+
48
+ - **A tool description is cut to 160 characters in `search_tools`** and to 240
49
+ in the describe path, both with a trailing `…`, unless the caller passes
50
+ `fullDescriptions: true`. Prose past those points reaches an agent only when
51
+ it pays for the expansion.
52
+ - **A compact schema renders into at most 1,024 UTF-8 bytes**, and any single
53
+ enum node into at most 256. Past either cap the renderer keeps what fits and
54
+ degrades the rest — a prefix of the enum plus `unknown`, a required-first
55
+ object with `unknown` types, or `unknown /* truncated */` — and flags the
56
+ match, which costs a describe round trip to recover.
57
+ - **`inputKeys`, `requiredInputKeys`, and `outputKeys` come only from bounded
58
+ plain-object schemas.** A top-level `anyOf` has no keys to list, so a caller
59
+ learns nothing about the arguments without expanding the schema.
60
+ - **A guide summary is capped at 120 characters**, defaulting to the guide's
61
+ first meaningful body line.
62
+ - **Search returns a connector's `id`, `title`, `guide`, and `guideSummary` —
63
+ never its `description`.** The description reaches an agent only as the
64
+ fallback summary for a guide with no usable body line. Routing facts belong
65
+ in the title and the guide's first line; a routing fact that lives only in
66
+ the connector description has been written into a field the model does not
67
+ read. Neither the `id` nor the `title` is a lexical document, so a term drawn
68
+ from one of them is not a search hit — it is a no-match whose guidance names
69
+ the connector and points at a scoped browse.
70
+
71
+ Two construction-time checks enforce the floor beneath all of this. The hard
72
+ one is `api()` itself: since
73
+ [#340](https://github.com/zackbart/connecta/issues/340) a hand-written tool
74
+ throws unless it carries a non-empty description, an explicit boolean
75
+ `annotations.readOnlyHint`, and an `inputSchema` — where it declares one — the
76
+ validator can compile. The soft one is `Registry.checkConventions()`, which
77
+ warns about a connector with no description, and about a static tool from any
78
+ other source with no description or no `inputSchema`.
79
+
80
+ ## Hand-written HTTP providers
81
+
82
+ Connecta owns the whole surface here, which means every miss is ours. These
83
+ apply to `api()`-based prebuilt connections (Cloudflare, Notion) and are the
84
+ bar any future one is written to.
85
+
86
+ None of them asks an author to re-derive transport safety. URL confinement,
87
+ query and body construction, `ctx.signal`, redirect refusal, credential
88
+ shadowing, bounded response reads, and network-failure normalization are the
89
+ [guarded fetch transport](./connectors.md#the-guarded-fetch-transport)'s job
90
+ ([#341](https://github.com/zackbart/connecta/issues/341)). What the conventions
91
+ below still demand — H11's error mapping and H12's credential test above all —
92
+ is the provider knowledge no shared helper can hold.
93
+
94
+ ### H1 — Identity is deployment-owned; the provider supplies everything else
95
+
96
+ The constructor takes an `id`, a required `purpose`, an optional `title`, and
97
+ optional `instructions` appended to — never replacing — the maintained guide.
98
+ A blank `purpose` throws at construction.
99
+
100
+ *Why:* an agent choosing between two instances of the same provider has only
101
+ the title and the guide summary to choose from. *Cost:* wrong-tool selection.
102
+
103
+ ### H2 — Names are `verb_object`, and the verb is the safety class
104
+
105
+ `snake_case`, a leading verb from the small set the surface already uses
106
+ (`list_`, `get_`, `search_`, `create_`, `update_`, `delete_`, plus a provider's
107
+ own vocabulary such as `purge_` or `trash_`), and a noun that matches the
108
+ provider's own word for the thing. A read never opens with a write verb, and a
109
+ write never hides behind a neutral one. Escape hatches are named
110
+ `<provider>_api_<class>` so they sort together and read as generic.
111
+
112
+ *Why:* the name is the only thing lexical search indexes besides the
113
+ description, and it is what an agent skims first. A connector's `id` and
114
+ `title` are displayed, not indexed — a search for one of them matches no tool,
115
+ and the miss is answered by guidance naming that connector rather than by a
116
+ ranked result. *Cost:* wrong-tool selection.
117
+
118
+ ### H3 — The selection sentence fits in 160 characters; the whole description in 240
119
+
120
+ Sentence one says what the tool returns or does, in the imperative, and is
121
+ complete inside 160 characters (roughly 40 tokens). Everything else — the
122
+ constraint, the disqualifier, the handoff — fits in the remaining 80 characters
123
+ (240 total, roughly 60 tokens). Detail that does not fit belongs in a property
124
+ description or the usage guide, both of which are fetched only when needed.
125
+
126
+ *Why:* search cuts at 160 and describe at 240 unless the caller passes
127
+ `fullDescriptions: true`, so anything past the budget is delivered only to an
128
+ agent that spends a second, larger read to get it. *Cost:* discovery tokens.
129
+
130
+ ### H4 — The description names the disqualifier, not the pitch
131
+
132
+ Say what the tool will not do when an agent is likely to assume it does:
133
+ `search` "finds pages and data sources by **title**. Never searches content."
134
+ One clause of honest negative space outperforms three of capability.
135
+
136
+ *Why:* the cheapest wrong call is the one never made. *Cost:*
137
+ wrong-tool selection.
138
+
139
+ ### H5 — Input schemas are complete, closed, and bounded
140
+
141
+ Every tool carries a hand-written `inputSchema`: a plain object at the top
142
+ level, `additionalProperties: false`, an accurate `required` list, an `enum` on
143
+ every constrained field, explicit numeric bounds on every page size and count,
144
+ and a description on every property — nested objects and array items included,
145
+ because a caller composing an array element is reading that element's fields,
146
+ not the parent's prose. `api()` enforces the enforceability half for free since
147
+ [#340](https://github.com/zackbart/connecta/issues/340): a schema the validator
148
+ cannot compile throws at construction, and one that only reveals itself on
149
+ first use fails the call rather than silently admitting unvalidated input — in
150
+ a surface we wrote ourselves, an unenforceable schema is our bug.
151
+
152
+ *Why:* a complete schema is the difference between one call and a call, a
153
+ rejection, and a repair. *Cost:* argument retries.
154
+
155
+ ### H6 — A local bound says whose bound it is
156
+
157
+ When a schema's bound is the provider's, the description says so; when the
158
+ bound is narrower than the provider's, the description says that too. A local
159
+ cap that an agent is told about beats a page size that fails somewhere inside
160
+ the provider — but only if the agent is told.
161
+
162
+ *Why:* an unexplained refusal reads as a bug and gets retried. *Cost:*
163
+ argument retries.
164
+
165
+ ### H7 — Schemas fit the compact renderer, or selection does not depend on the part that is cut
166
+
167
+ Keep the common path's compact input and output shapes inside 1,024 bytes and
168
+ each enum node inside 256. Where a legitimate enum genuinely cannot fit — 21
169
+ DNS record types — the truncation is acceptable only if the tool's name and
170
+ description already carry enough for selection, so the caller expands the
171
+ schema to *call*, not to *choose*.
172
+
173
+ *Why:* a truncated compact shape costs a describe round trip. *Cost:* discovery
174
+ tokens.
175
+
176
+ ### H8 — Every tool declares an `outputSchema`
177
+
178
+ Declared outputs are what produce `outputKeys` and the `fields` projection's
179
+ `availableFields`, and they let a program reduce a result without first
180
+ fetching one to look at. Connecta measured *declared* output schemas at 0 of 30
181
+ tools on one real deployment and 3 of 30 on another
182
+ ([#282](https://github.com/zackbart/connecta/issues/282)) — nearly every tool an
183
+ agent meets is a shape it can only learn by calling. A maintained provider has
184
+ no excuse to join that majority.
185
+
186
+ *Why:* an agent that knows the shape projects before it reads. *Cost:* result
187
+ size.
188
+
189
+ ### H9 — Every read projects, and says what it dropped
190
+
191
+ Reads return the provider's payload flattened and renamed, with plan,
192
+ permission, and presentation noise removed. Where the dropped detail can
193
+ matter, the tool takes `raw: true` and returns the untouched response; where
194
+ the provider itself truncated something, the projection surfaces that fact and
195
+ the id needed to fetch the rest, rather than handing back a confident partial.
196
+ The argument and result vocabulary is consistent within a connector, and the
197
+ mapping from the provider's own names is either identity or one mechanical rule
198
+ stated in the guide.
199
+
200
+ *Why:* projection is the largest single lever on what an agent reads, and a
201
+ silent truncation is worse than a large result. *Cost:* result size.
202
+
203
+ ### H10 — Pagination is one convention per connector, with one field to branch on
204
+
205
+ List tools take an explicit page argument and a cursor, default to a page size
206
+ smaller than the provider's maximum because a first read should be cheap, and
207
+ return exactly one branchable signal — `hasMore` beside a cursor. Cursors are
208
+ opaque: passed back verbatim, never parsed or constructed. Where an endpoint
209
+ paginates differently from the rest of the connector, the schema and the guide
210
+ both say so instead of letting an agent discover it.
211
+
212
+ *Why:* the loop condition should not be a research project. *Cost:* result size.
213
+
214
+ ### H11 — Errors are mapped to what the caller does next
215
+
216
+ The typed failure code is chosen by the caller's next move, not by the
217
+ provider's name for what happened: an authorization gap an operator must fix is
218
+ not `auth_required` if `authorize_connector` cannot fix it; a retryable failure
219
+ carries `retryAfterMs` when the provider says how long; an ambiguous provider
220
+ code gets a message that states the ambiguity rather than picking the
221
+ convenient reading. A call that can only fail is refused locally as
222
+ `invalid_args` before the round trip. Provider error prose is never parsed to
223
+ invent a classification.
224
+
225
+ **A downstream 404 is `not_found` — when the provider means it.** The code
226
+ exists because the next move is none of the others': you do not wait, you do
227
+ not send the agent to `authorize_connector`, you do not repair the argument
228
+ object. You re-address — look the id up again, or accept the absence and carry
229
+ on — and a program looping over ids inside `execute_code` can continue past
230
+ `not_found` where `connector_call_failed` would have to abort the run, reading
231
+ the code off a `connecta.batch` entry's `errorDetails` rather than off a caught
232
+ error, which the guest bridge has already stripped to prose. That
233
+ control-flow difference is the H11 test being met; it is not a label for the
234
+ cause.
235
+
236
+ The qualifier is the whole rule. Map a status to `not_found` only where the
237
+ provider distinguishes absence from a permission gap. Where it does not —
238
+ Notion returns `object_not_found` both for an object that is gone and for one
239
+ that was never shared with the integration, and will not say which — the honest
240
+ code stays `connector_call_failed` (or `auth_required`, where a credential
241
+ really is the fix) and the message states the ambiguity, exactly as the
242
+ paragraph above requires. Cloudflare is the other side of the pair: a token
243
+ that may not touch a resource is refused with 401 or 403, so its 404 is an
244
+ absence and maps to `not_found`. Neither connector's mapping changed shape when
245
+ the code arrived; one of them changed codes.
246
+
247
+ Two boundaries. `not_found` is about a resource the *downstream* owns: an
248
+ address connecta cannot resolve is already framed as `unknown_address` or
249
+ `unknown_tool` and never reaches a connector. And it never appears on the
250
+ hosted-MCP proxy path, because `P1` forbids re-shaping downstream framing and
251
+ prose is never parsed to invent a classification — a proxied server's own
252
+ missing-resource error arrives as that server wrote it. The two paths do not
253
+ diverge on the rule; they diverge on who is entitled to apply it, which is the
254
+ same split every other code already has.
255
+
256
+ *Why:* a misrouted error sends an agent down a recovery path that cannot
257
+ succeed. *Cost:* argument retries.
258
+
259
+ ### H12 — One operator credential, one cheap test, no probing
260
+
261
+ The connection declares its credential slot with a labeled field per secret and
262
+ implements `testCredential`/`testCredentials` with the cheapest call that proves
263
+ the secret is live, reporting the identity or workspace it authenticated as.
264
+ Connecta does not check credentials behind an operator's back; the test runs
265
+ when a human asks, and everything else fails loudly at use.
266
+
267
+ *Why:* "which account is this?" answered once at configuration time is a
268
+ question no agent has to answer by calling something. *Cost:* wrong-tool
269
+ selection.
270
+
271
+ ### H13 — The guide carries only what a schema cannot
272
+
273
+ `usageGuide` uses the structured form: `content`, an explicit `summary`, and
274
+ `required: true` only when correct use depends on a sequence or convention no
275
+ complete schema can express. Imperative bullets, decision first, exact tool and
276
+ argument names, constraints with their numbers. The first content line is the
277
+ routing fact, because it is the summary fallback. Nothing in the guide restates
278
+ a schema.
279
+
280
+ *Why:* the guide is fetched into a live context window, so every line that
281
+ repeats a schema is paid for twice. *Cost:* discovery tokens.
282
+
283
+ ### H14 — A named tool must beat the escape hatch, and the escape hatch splits by safety
284
+
285
+ Guarded raw access is an accepted shape, not a required one — a small provider
286
+ whose surface is genuinely finite may deliberately have none, and say so. Where
287
+ a provider is large and fast-moving enough to need one, it is split by safety
288
+ class: a GET-only tool that is explicitly read-only, a JSON mutation tool that
289
+ is always destructive, and an upload tool that is always destructive. The split
290
+ is Connecta's, never an agent-supplied HTTP method. Paths are provider-relative
291
+ and confined; the connector owns authentication, host, content type, and
292
+ framing. This is not schema ingestion — a fixed handful of stable tools, never
293
+ one per operation — and a *named* tool earns its place only by beating the
294
+ hatch on schema, projection, or safety routing.
295
+
296
+ *Why:* every named tool costs catalog bytes forever, and a thin wrapper around
297
+ a call the hatch already makes costs them for nothing. *Cost:* discovery
298
+ tokens.
299
+
300
+ ## Hosted-MCP proxies
301
+
302
+ Here the downstream owns the tool names, descriptions, input schemas, result
303
+ shapes, pagination, and error prose. Conventions that legislate those things
304
+ would be fiction. What Connecta owns is the endpoint, the credential, the
305
+ classification, the connector's own identity, the guide, and the budget — so
306
+ that is what these conventions cover.
307
+
308
+ ### P1 — Normalize by adding, never by rewriting
309
+
310
+ A proxy may add annotations, a title, a guide, and an admission policy. It does
311
+ not rewrite a downstream tool's name, description, or schema, and it does not
312
+ re-shape a downstream result. A rewritten description drifts silently away from
313
+ the schema it describes, and the agent believes the description.
314
+
315
+ *Why:* the catalog must stay a true report of what the downstream will accept.
316
+ *Cost:* argument retries.
317
+
318
+ ### P2 — Identity is deployment-owned
319
+
320
+ Identical to H1: `id`, required `purpose`, optional `title`, and `instructions`
321
+ appended to the maintained guide, never replacing it, and never able to change
322
+ the safety classification.
323
+
324
+ *Why:* two instances of the same provider are told apart only by title and
325
+ guide summary. *Cost:* wrong-tool selection.
326
+
327
+ ### P3 — The fact that decides routing goes in the title and the guide's first line
328
+
329
+ Whichever variant an agent must not get wrong — production versus sandbox,
330
+ read-only versus read-write, region, account — appears in the default `title`
331
+ and as the first content line of the guide. It may also appear in the
332
+ `description`; it may never appear *only* there, because search never returns
333
+ the description.
334
+
335
+ *Why:* the model reads title and guide summary at browse time and nothing else.
336
+ *Cost:* wrong-tool selection.
337
+
338
+ ### P4 — Endpoint selection is a constructor option with the safest honest default
339
+
340
+ Where the provider publishes more than one endpoint, the option selects between
341
+ them and the default is the safe one. Where the provider publishes one endpoint
342
+ and the environment rides the credential, the mode is required with no default,
343
+ and construction throws when a recognizable credential contradicts the declared
344
+ mode. Deprecated transports stay unreachable.
345
+
346
+ *Why:* a provider-enforced scope limit is a stronger guarantee than any
347
+ annotation Connecta applies, and a wrong-mode write is not recoverable by
348
+ retrying. *Cost:* wrong-tool selection.
349
+
350
+ ### P5 — Classification is a reviewed allowlist that fails closed
351
+
352
+ Reads are listed by name, writes are listed by name with their destructive
353
+ verdict, and anything unlisted is not read-only. The lists are supersets:
354
+ hosted catalogs vary by plan and feature flags, so a classified name a
355
+ workspace never returns costs nothing while an unclassified new one fails
356
+ closed onto `call_destructive_tool`. The classification fills in downstream
357
+ silence and otherwise preserves explicit annotations, with the single
358
+ fail-closed exception the [ethos](../ethos.md) accepted-prebuilt row records
359
+ ([#315](https://github.com/zackbart/connecta/issues/315)) — a release-reviewed
360
+ destructive verdict outranks a contradictory `readOnlyHint: true`, because that
361
+ release independently established that the tool mutates existing state. An
362
+ additive write leaves `destructiveHint` unset.
363
+
364
+ *Why:* the fail-closed read-only invariant is not negotiable, and inflated
365
+ destructive copy trains humans to approve without reading. *Cost:* wrong-tool
366
+ selection.
367
+
368
+ ### P6 — The guide says the catalog is not a fixed set
369
+
370
+ The guide tells the agent to search this connector for what the workspace
371
+ actually exposes rather than assuming a documented tool exists, and names the
372
+ plan- or beta-gated areas where absence is expected.
373
+
374
+ *Why:* a hosted catalog varies per account, and an agent that assumes ours is
375
+ complete spends calls proving it is not. *Cost:* wrong-tool selection.
376
+
377
+ ### P7 — The guide carries the reduction advice the schemas cannot
378
+
379
+ A proxy cannot project a downstream result, so the guide tells the agent to
380
+ page with the cursor rather than raising the page size, and to reduce inside
381
+ `execute_code` before returning anything. Structured form, explicit `summary`,
382
+ `required: true` only for a genuine cross-tool sequence or a generic wrapper.
383
+
384
+ *Why:* the only projection available is the one the program writes. *Cost:*
385
+ result size.
386
+
387
+ ### P8 — Identity resolution comes before action
388
+
389
+ Where a downstream's write arguments take ids, the guide names the read tools
390
+ that produce them and says not to guess. Where the provider has a
391
+ human-readable identifier alongside a UUID, the guide says which is which.
392
+
393
+ *Why:* a guessed id is a call, a rejection, and a repair. *Cost:* argument
394
+ retries.
395
+
396
+ ### P9 — Authentication defaults to OAuth, with a documented headless alternative
397
+
398
+ OAuth per connector instance, stored in connector-scoped storage, is the
399
+ default. The provider's own headless credential — a personal API key, a
400
+ restricted key, a service account — is supported through explicit `headers`
401
+ auth, documented as a secret rather than configuration, and paired with the
402
+ narrowest mode the deployment can use. `requireHttps` is set. Recovery from an
403
+ expired authorization is the ordinary `auth_required` → `authorize_connector`
404
+ route.
405
+
406
+ *Why:* one route back from an expired credential is what keeps a failed call
407
+ from becoming an abandoned task. *Cost:* wrong-tool selection.
408
+
409
+ ### P10 — There is no credential test; the equivalent check happens at construction
410
+
411
+ A proxy declares no operator credential slot and implements neither
412
+ `testCredential` nor `testCredentials`. `remoteMcp()` has no `credential`
413
+ option, and neither shape of proxy credential is vault-managed: OAuth lives in
414
+ connector-scoped storage and is exercised by the authorization flow itself,
415
+ while a headless key arrives as deployment configuration in `headers`, so there
416
+ is nothing for the operator credentials page to hold or test. H12's guarantee is
417
+ still owed, and a proxy pays it in two other places: construction throws when a
418
+ recognizable credential contradicts the declared mode (P4), and a dead or
419
+ revoked credential fails loudly at use as `auth_required` with the
420
+ `authorize_connector` route attached (P9). Connecta never probes a downstream to
421
+ see whether a credential is still alive — that shape is `removed` in the ethos
422
+ ([#179](https://github.com/zackbart/connecta/issues/179)). A provider that later
423
+ does take a vault-managed secret inherits H12 whole.
424
+
425
+ *Why:* an unasked-for liveness probe spends a call on every deployment to answer
426
+ a question only a misconfigured one has. *Cost:* result size.
427
+
428
+ ### P11 — Connecta classifies the transport; the downstream owns the tool error
429
+
430
+ Connecta maps what it can see from outside the tool: an authorization failure to
431
+ `auth_required`, a session or scope teardown, a timeout, and a capability the
432
+ proxy will not relay (`input_required`, task-required execution) to an explicit
433
+ refusal that says so. A tool-level failure the downstream returns — a validation
434
+ complaint, a not-found, a plan restriction — is passed back as it arrived. The
435
+ proxy does not read downstream error prose to invent a Connecta classification,
436
+ and does not repackage a downstream error as `invalid_args`, because it has no
437
+ schema of its own to have validated against. Where a downstream reliably reports
438
+ a retryable condition, the guide says how to recognize it rather than the code
439
+ guessing.
440
+
441
+ *Why:* a transport failure and a rejected argument need different next moves,
442
+ and prose-sniffing routes the second one down the first one's path. *Cost:*
443
+ argument retries.
444
+
445
+ ### P12 — Declare an admission budget only when the provider documents a number
446
+
447
+ Where the provider publishes a rate limit, transcribe it as a rolling-window
448
+ budget and say in the guide that it is a per-runtime approximation, not an
449
+ enforcement. Where the provider documents nothing, or documents a limit metered
450
+ per user in a way a per-runtime counter cannot approximate, declare no budget
451
+ and leave the number to the operator who knows the account — with a documented
452
+ example of how to supply one. A `maxConcurrency` beside a budget is a choice
453
+ Connecta made and is labeled as one.
454
+
455
+ *Why:* a hardcoded ceiling either throttles a healthy deployment or fails to
456
+ protect a busy one, and both look like the provider being flaky. *Cost:*
457
+ argument retries.
458
+
459
+ ### P13 — A drifting downstream must be visible, not absorbed
460
+
461
+ The classification lists name what a release reviewed, and they are the
462
+ manifest the runtime drift check compares against — one structure per provider,
463
+ built once by `vettedCatalog()` and used both to classify and to compare, so
464
+ the annotation a caller gets and the verdict a check reads can never disagree.
465
+ When the downstream changes underneath them, the correct outcome is a loud
466
+ unclassified tool on the approval path and a maintained record of the drift —
467
+ never a quiet re-guess. The runtime half is
468
+ [the runtime drift policy](#the-runtime-drift-policy) below; the release-time
469
+ half is [the maintainer-run drift check](#the-maintainer-run-drift-check).
470
+
471
+ *Why:* an allowlist nobody can tell is stale is an allowlist that is wrong.
472
+ *Cost:* wrong-tool selection.
473
+
474
+ ## The runtime drift policy
475
+
476
+ Detection rides a refresh; it never causes one
477
+ ([#343](https://github.com/zackbart/connecta/issues/343)). The comparison
478
+ happens inside the wrapper's `listTools`, on the listing the downstream just
479
+ returned to serve a request the deployment already made, before the
480
+ classification is applied — so what it reads is the downstream's own word, not
481
+ connecta's fill-in. There is no scheduled job, no background request, no
482
+ credential probe, and no automatic issue filing. Proactive credential liveness
483
+ stays removed ([#179](https://github.com/zackbart/connecta/issues/179)); this
484
+ is the shape that does not become it.
485
+
486
+ **What a manifest holds.** Every tool name a release reviewed, the verdict it
487
+ reviewed it as (`read-only`, `additive`, `destructive`), and — where a release
488
+ actually read them — a digest of that tool's input and output schemas. Today
489
+ the three proxies ship names and verdicts and no digests, because no release
490
+ has read a live schema and written it down, and an invented digest reports a
491
+ change that never happened. `npm run drift:check -- --record` reads them from a
492
+ live catalog and prints the block a release pastes in; until a release does,
493
+ a manifest without digests counts no schema changes, which is the honest answer
494
+ rather than a silent zero.
495
+
496
+ **What it counts.** Four categories, and only counts:
497
+
498
+ | Category | What it means |
499
+ | --- | --- |
500
+ | unclassified additions | the downstream serves a tool no release classified; it already fails closed onto `call_destructive_tool` |
501
+ | names no longer served | a classified name is absent from this catalog |
502
+ | annotation conflicts | the downstream *explicitly* contradicts a vetted verdict — `readOnlyHint: false` or `destructiveHint: true` on a vetted read, `readOnlyHint: true` on a vetted write |
503
+ | schema changes | a recorded digest no longer matches the schemas that arrived |
504
+
505
+ Silence is never a conflict: filling it in is what the classification is for.
506
+ A non-zero "no longer served" count is the expected reading on a plan-gated
507
+ workspace, because P5's lists are deliberate supersets — it is triage input,
508
+ not an alarm.
509
+
510
+ **Where it surfaces.** Connector status carries the counts and the time they
511
+ were observed; `/health` carries the same per connector, which is where
512
+ `connecta doctor` reads them, and doctor reports drift without failing on it.
513
+ Both reads are projections — four counts and a bounded timestamp, rebuilt from
514
+ whatever the connector seam returned, because `/health` is unauthenticated and
515
+ `Connector.catalogDrift()` is third-party code.
516
+ One activity event per *change* in the counts — an identical report every TTL
517
+ is a heartbeat, not news — carrying the connector id and four integers. The
518
+ event type has nowhere to put a tool name, a schema, an argument, a result, or
519
+ downstream error prose, which is the same construction guarantee the tool-call
520
+ event makes. Which tool drifted is deliberately absent from the runtime: it is
521
+ answered by the maintainer-run check, with a live catalog in front of it.
522
+
523
+ **How far an observation reaches.** One runtime, and no further. The
524
+ observation lives in the isolate or process that served the refresh; unlike the
525
+ catalog, it is not persisted, so nothing carries it across a Workers isolate, a
526
+ restart, or a second Node process. Status and `/health` therefore answer for
527
+ the instance that took the request: on Workers a `connecta doctor` run will
528
+ usually land on an isolate that has served no refresh and print nothing, and
529
+ behind more than one process it is a coin flip. Read an empty report as *this
530
+ runtime has observed nothing*, never as *nothing drifted* — the durable record
531
+ of a finding is the activity event a sink already stored, and naming the tool
532
+ is still the maintainer-run check's job.
533
+
534
+ **What a finding obliges.** A contradicted vetted verdict — the downstream
535
+ calling a release-reviewed destructive tool `readOnlyHint: true`, or a vetted
536
+ read `destructiveHint: true` — blocks that provider's next release until a
537
+ human has re-reviewed the tool. Everything else enters ordinary issue triage.
538
+ No finding changes what a caller may reach: an unclassified tool fails closed
539
+ whether or not anybody noticed it arrived.
540
+
541
+ ## The maintainer-run drift check
542
+
543
+ `npm run drift:check` is the other half
544
+ ([#351](https://github.com/zackbart/connecta/issues/351)): a human at a laptop,
545
+ before a release, with local credentials and the published specifications in
546
+ front of them. It lives in
547
+ [`scripts/drift-check.mjs`](https://github.com/zackbart/connecta/blob/main/scripts/drift-check.mjs) and ships nowhere —
548
+ `scripts/` is outside the package, no runtime module imports it, and nothing it
549
+ reads becomes a runtime input.
550
+
551
+ **Hosted-MCP catalogs.** `--hosted` lists each proxy's live catalog with the
552
+ maintainer's own key and diffs it against the same `vettedCatalog()` manifest
553
+ the connector classifies from, reporting tools *by name*: added, no longer
554
+ served, annotation conflicts with what the downstream actually claimed, and —
555
+ once a manifest records schema digests — which tool's schemas moved. The names
556
+ live here rather than in the runtime because the runtime's counts are
557
+ payload-free by construction, and a name has no reader there anyway. It then
558
+ compares its own totals against `detectCatalogDrift()`: two readings of one
559
+ manifest that disagree mean one of them is lying, which is worth failing over.
560
+ One credential per provider comes from the environment —
561
+ `CONNECTA_DRIFT_LINEAR_KEY`, `CONNECTA_DRIFT_STRIPE_KEY`,
562
+ `CONNECTA_DRIFT_MIXPANEL_KEY` — and a missing or dead one stops the run with a
563
+ message naming it rather than reporting an empty catalog as mass removal.
564
+
565
+ **Touched endpoints.** A hand-written provider is written against a published
566
+ OpenAPI document and calls a few dozen of its operations, so
567
+ [`scripts/drift/`](https://github.com/zackbart/connecta/tree/main/scripts/drift) commits exactly those: method, path, the
568
+ specification revision a release reviewed the endpoint at, whether the
569
+ operation was deprecated at that revision, and a digest of that endpoint's
570
+ contract. `--specs` fetches each provider's published document and reports four
571
+ things per touched endpoint — the path is gone, the method is gone, the
572
+ operation's deprecation changed, or its contract changed since the recorded
573
+ revision. Everything else in the document is ignored, which is the point: a
574
+ Cloudflare release that rewrites 2,000 operations connecta never calls is not
575
+ news, and a revision bump that left the touched contracts alone reports
576
+ nothing.
577
+
578
+ Deprecation is reported as a *transition*, not a state: a deprecation a
579
+ maintainer has read and recorded stops being news, and an operation that comes
580
+ back off the deprecation list is its own finding. Without that, a single
581
+ reviewed deprecation would fail every release forever, and the check could
582
+ never reach the "no drift" state its exit code is for.
583
+
584
+ A contract digest covers the parameters, the request body, and the success
585
+ responses, with local `$ref`s inlined so a change inside a shared component is
586
+ visible, and with descriptions, examples, and `x-` extensions stripped so a
587
+ reworded document is not a finding. Inlining runs before a response's `content`
588
+ is read, because a whole response object is often a reference itself —
589
+ Cloudflare writes several of connecta's touched responses that way — and
590
+ reading through the reference would digest the response contract as nothing at
591
+ all. Two bounds are deliberate: a `$ref` cycle stays a reference rather than an
592
+ infinite walk, and failure responses are excluded because an error body is
593
+ H11's business, mapped from the status. `--record` rewrites the manifests from
594
+ the documents on hand; run it when a finding has been reviewed, and read the
595
+ diff before committing it.
596
+
597
+ Narrowing is checked against the half being run: `--specs --provider linear`
598
+ and `--hosted --provider notion` exit 2 rather than checking nothing and
599
+ reporting no drift, because a false green from a plausible typo is the one
600
+ failure mode a release-time exit code cannot afford.
601
+
602
+ **What it never does.** No downstream credential reaches CI. No scheduled job,
603
+ no background traffic in a deployment, no automatic issue filing. A finding is
604
+ read by a human and becomes a GitHub issue they wrote, because the decision a
605
+ finding needs — the provider moved this endpoint, or connecta has to stop
606
+ calling it — is not one a diff can make. Published specifications remain drift
607
+ evidence and nothing else: no tool is generated from one, which is the
608
+ [ethos](../ethos.md)'s refusal, not a detail of this script.
609
+
610
+ ## What the audit checks
611
+
612
+ The provider audit ([#342](https://github.com/zackbart/connecta/issues/342))
613
+ runs this document against each of the five providers and returns a verdict per
614
+ convention: **meets**, **misses** (with the fix), or **not applicable** (with
615
+ the reason). A convention is never quietly skipped, and an accepted miss is
616
+ recorded as a provider-specific exception with its argument, not left blank.
617
+ Its five reports live in [provider-audit.md](./provider-audit.md), and the
618
+ mechanically checkable half of the hand-written bar runs on every test run in
619
+ [`test/provider-conventions.test.ts`](https://github.com/zackbart/connecta/blob/main/test/provider-conventions.test.ts) —
620
+ so a convention that was met once stays met, or fails loudly.
621
+
622
+ Hand-written providers are audited against H1–H14; hosted-MCP proxies against
623
+ P1–P13. Applying a hand-written convention to a proxy is a category error, not
624
+ a finding.
625
+
626
+ Most of the bar is mechanically checkable against the shipped surface rather
627
+ than by reading:
628
+
629
+ | Convention | Mechanical check |
630
+ | --- | --- |
631
+ | H1, P2 | constructor throws on a blank `purpose`; `instructions` appear appended to the guide |
632
+ | H2 | every tool name matches `^[a-z][a-z0-9_]*$` and opens with a verb from the connector's own set |
633
+ | H3 | first sentence ≤ 160 characters; whole description ≤ 240 |
634
+ | H5 | every tool has an `inputSchema` that is a closed plain object with a `required` list, and every property at every depth — nested objects and array items included — carries a description or sits on a recorded exception list; `api()` refuses to construct one it cannot enforce |
635
+ | H7 | every compact input and output render stays inside 1,024 bytes, or the tool is on a recorded exception list |
636
+ | H8 | every tool declares an `outputSchema` |
637
+ | H9 | every read either projects or documents why it does not; `raw: true` exists wherever the projection drops something recoverable |
638
+ | H10 | every list tool has bounded page arguments, a default below the provider maximum, and exactly one `hasMore`-shaped signal |
639
+ | H11 | every mapped provider status has a test asserting the resulting code and retryability |
640
+ | H12 | `credential` declared with labeled fields, and `testCredential`/`testCredentials` implemented |
641
+ | H13, P7 | `usageGuide` uses the structured form with an explicit `summary`; `required` is set only with a stated reason |
642
+ | H14 | escape hatches split GET / JSON-mutate / upload, and the GET tool is annotated read-only |
643
+ | P1 | the wrapper's `listTools` changes annotations only |
644
+ | P3 | the routing fact appears in the default `title` and the guide's first content line |
645
+ | P4 | endpoint or mode option exists, with the documented default (or no default, where none is safe) |
646
+ | P5 | reads and writes are named lists; an unlisted tool resolves to not-read-only; a reviewed destructive name beats a contradictory `readOnlyHint: true` |
647
+ | P6, P8 | the guide contains the catalog-varies note and the id-resolution rule |
648
+ | P9 | `auth` defaults to OAuth and `requireHttps` is set |
649
+ | P10 | no `credential`, `testCredential`, or `testCredentials` on the wrapper; the mode/key contradiction throws at construction instead |
650
+ | P11 | an authorization failure surfaces as `auth_required`; a downstream tool error is returned unchanged, with no code chosen from its prose |
651
+ | P12 | a declared budget matches a citable documented limit, or the absence is justified in the guide |
652
+ | P13 | classification lists are maintained in one place per provider and built into the manifest the wrapper classifies from, so the drift check compares against the same fact the caller is served |
653
+
654
+ The remainder — H4, H6, and the judgment in H14 about whether a named tool
655
+ beats the escape hatch — is a reading, and the audit reports it as one. The
656
+ Cloudflare keep/prune half of that judgment was made in
657
+ [#350](https://github.com/zackbart/connecta/issues/350): 30 keep, 18 improve,
658
+ 3 prune, measured per tool in
659
+ [`eval/current-version/results/issue-350-evidence.md`](https://github.com/zackbart/connecta/blob/main/eval/current-version/results/issue-350-evidence.md).
660
+ Its eighteen `improve` rows are H8 and H9 misses on tools that clearly earn
661
+ their place, so they are this audit's work, not a second removal argument.
662
+
663
+ Each provider's own guide ([Cloudflare](./cloudflare.md),
664
+ [Linear](./linear.md), [Mixpanel](./mixpanel.md), [Notion](./notion.md),
665
+ [Stripe](./stripe.md)) is part of the audited surface: documentation moves with
666
+ the work, and a guide describing a surface that shipped differently is itself a
667
+ miss.