@zackbart/connecta 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/AGENTS.md +28 -15
  2. package/CHANGELOG.md +493 -0
  3. package/README.md +19 -4
  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 +0 -1
  27. package/dist/catalog.d.ts +0 -1
  28. package/dist/catalog.js +0 -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 +0 -1
  42. package/dist/errors.js +0 -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 +5 -2
  78. package/dist/providers/cloudflare.js +435 -328
  79. package/dist/providers/linear.d.ts +26 -5
  80. package/dist/providers/linear.js +32 -60
  81. package/dist/providers/mixpanel.d.ts +35 -4
  82. package/dist/providers/mixpanel.js +53 -78
  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 -59
  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 +95 -27
  129. package/documentation/code-mode.md +12 -12
  130. package/documentation/connector-guides.md +172 -5
  131. package/documentation/connectors.md +98 -7
  132. package/documentation/linear.md +26 -9
  133. package/documentation/mcp-ui-design.md +2 -2
  134. package/documentation/mixpanel.md +48 -16
  135. package/documentation/notion.md +16 -0
  136. package/documentation/operations.md +283 -5
  137. package/documentation/operator-ui.md +120 -5
  138. package/documentation/provider-audit.md +160 -0
  139. package/documentation/provider-conventions.md +631 -0
  140. package/documentation/request-admission.md +167 -5
  141. package/documentation/rich-output-design.md +1 -2
  142. package/documentation/stripe.md +27 -12
  143. package/ethos.md +14 -9
  144. package/examples/worker/README.md +96 -12
  145. package/examples/worker/src/index.ts +46 -1
  146. package/examples/worker/wrangler.jsonc +14 -1
  147. package/package.json +4 -5
  148. package/templates/node/.dockerignore +10 -0
  149. package/templates/node/.env.example +40 -3
  150. package/templates/node/AGENTS.md +11 -1
  151. package/templates/node/Dockerfile +49 -0
  152. package/templates/node/README.md +117 -2
  153. package/templates/node/docker-compose.yml +39 -0
  154. package/templates/node/package.json +1 -1
  155. package/templates/node/src/file-activity.ts +182 -0
  156. package/templates/node/src/index.ts +80 -4
  157. package/assets/connecta-clay-hero.png +0 -0
  158. package/dist/access-tokens.d.ts.map +0 -1
  159. package/dist/access-tokens.js.map +0 -1
  160. package/dist/activity.d.ts.map +0 -1
  161. package/dist/activity.js.map +0 -1
  162. package/dist/apps-shell.d.ts.map +0 -1
  163. package/dist/apps-shell.js.map +0 -1
  164. package/dist/auth/bearer.d.ts.map +0 -1
  165. package/dist/auth/bearer.js.map +0 -1
  166. package/dist/auth/clerk.d.ts.map +0 -1
  167. package/dist/auth/clerk.js.map +0 -1
  168. package/dist/auth/downstream-oauth.d.ts.map +0 -1
  169. package/dist/auth/downstream-oauth.js.map +0 -1
  170. package/dist/call-admission.d.ts.map +0 -1
  171. package/dist/call-admission.js.map +0 -1
  172. package/dist/catalog-fingerprint.d.ts.map +0 -1
  173. package/dist/catalog-fingerprint.js.map +0 -1
  174. package/dist/catalog-limits.d.ts.map +0 -1
  175. package/dist/catalog-limits.js.map +0 -1
  176. package/dist/catalog-service.d.ts.map +0 -1
  177. package/dist/catalog-service.js.map +0 -1
  178. package/dist/catalog.d.ts.map +0 -1
  179. package/dist/catalog.js.map +0 -1
  180. package/dist/concurrency.d.ts.map +0 -1
  181. package/dist/concurrency.js.map +0 -1
  182. package/dist/connector-scope.d.ts.map +0 -1
  183. package/dist/connector-scope.js.map +0 -1
  184. package/dist/connectors/api.d.ts.map +0 -1
  185. package/dist/connectors/api.js.map +0 -1
  186. package/dist/connectors/remote-mcp.d.ts.map +0 -1
  187. package/dist/connectors/remote-mcp.js.map +0 -1
  188. package/dist/credentials.d.ts.map +0 -1
  189. package/dist/credentials.js.map +0 -1
  190. package/dist/errors.d.ts.map +0 -1
  191. package/dist/errors.js.map +0 -1
  192. package/dist/execute.d.ts.map +0 -1
  193. package/dist/execute.js.map +0 -1
  194. package/dist/executor-admission.d.ts.map +0 -1
  195. package/dist/executor-admission.js.map +0 -1
  196. package/dist/executor-result.d.ts.map +0 -1
  197. package/dist/executor-result.js.map +0 -1
  198. package/dist/executors/quickjs-child.d.ts.map +0 -1
  199. package/dist/executors/quickjs-child.js.map +0 -1
  200. package/dist/executors/quickjs-protocol.d.ts.map +0 -1
  201. package/dist/executors/quickjs-protocol.js.map +0 -1
  202. package/dist/executors/quickjs-runtime.d.ts.map +0 -1
  203. package/dist/executors/quickjs-runtime.js.map +0 -1
  204. package/dist/executors/quickjs.d.ts.map +0 -1
  205. package/dist/executors/quickjs.js.map +0 -1
  206. package/dist/favicon.d.ts.map +0 -1
  207. package/dist/favicon.js.map +0 -1
  208. package/dist/index.d.ts.map +0 -1
  209. package/dist/index.js.map +0 -1
  210. package/dist/invocation.d.ts.map +0 -1
  211. package/dist/invocation.js.map +0 -1
  212. package/dist/json-schema.d.ts.map +0 -1
  213. package/dist/json-schema.js.map +0 -1
  214. package/dist/mcp-result.d.ts.map +0 -1
  215. package/dist/mcp-result.js.map +0 -1
  216. package/dist/meta-tools.d.ts.map +0 -1
  217. package/dist/meta-tools.js.map +0 -1
  218. package/dist/node.d.ts.map +0 -1
  219. package/dist/node.js.map +0 -1
  220. package/dist/operator-ui/generated.d.ts.map +0 -1
  221. package/dist/operator-ui/generated.js.map +0 -1
  222. package/dist/operator-ui/model.d.ts.map +0 -1
  223. package/dist/operator-ui/model.js.map +0 -1
  224. package/dist/providers/cloudflare.d.ts.map +0 -1
  225. package/dist/providers/cloudflare.js.map +0 -1
  226. package/dist/providers/linear.d.ts.map +0 -1
  227. package/dist/providers/linear.js.map +0 -1
  228. package/dist/providers/mixpanel.d.ts.map +0 -1
  229. package/dist/providers/mixpanel.js.map +0 -1
  230. package/dist/providers/notion.d.ts.map +0 -1
  231. package/dist/providers/notion.js.map +0 -1
  232. package/dist/providers/stripe.d.ts.map +0 -1
  233. package/dist/providers/stripe.js.map +0 -1
  234. package/dist/registry.d.ts.map +0 -1
  235. package/dist/registry.js.map +0 -1
  236. package/dist/retired-toolkits.d.ts.map +0 -1
  237. package/dist/retired-toolkits.js.map +0 -1
  238. package/dist/routes/access-tokens.d.ts.map +0 -1
  239. package/dist/routes/access-tokens.js.map +0 -1
  240. package/dist/routes/activity.d.ts.map +0 -1
  241. package/dist/routes/activity.js.map +0 -1
  242. package/dist/routes/credentials.d.ts.map +0 -1
  243. package/dist/routes/credentials.js.map +0 -1
  244. package/dist/routes/mcp.d.ts.map +0 -1
  245. package/dist/routes/mcp.js.map +0 -1
  246. package/dist/routes/oauth.d.ts.map +0 -1
  247. package/dist/routes/oauth.js.map +0 -1
  248. package/dist/routes/shared.d.ts.map +0 -1
  249. package/dist/routes/shared.js.map +0 -1
  250. package/dist/routes/ui.d.ts.map +0 -1
  251. package/dist/routes/ui.js.map +0 -1
  252. package/dist/server.d.ts.map +0 -1
  253. package/dist/server.js.map +0 -1
  254. package/dist/skills.d.ts.map +0 -1
  255. package/dist/skills.js.map +0 -1
  256. package/dist/storage/file.d.ts.map +0 -1
  257. package/dist/storage/file.js.map +0 -1
  258. package/dist/storage/memory.d.ts.map +0 -1
  259. package/dist/storage/memory.js.map +0 -1
  260. package/dist/timeout.d.ts.map +0 -1
  261. package/dist/timeout.js.map +0 -1
  262. package/dist/tool-safety.d.ts.map +0 -1
  263. package/dist/tool-safety.js.map +0 -1
  264. package/dist/types.d.ts.map +0 -1
  265. package/dist/types.js.map +0 -1
  266. package/dist/ui.d.ts.map +0 -1
  267. package/dist/ui.js.map +0 -1
  268. package/dist/validate.d.ts.map +0 -1
  269. package/dist/validate.js.map +0 -1
  270. package/dist/version.d.ts.map +0 -1
  271. package/dist/version.js.map +0 -1
  272. package/examples/node/README.md +0 -52
  273. package/examples/node/src/index.ts +0 -71
  274. package/src/access-tokens.ts +0 -289
  275. package/src/activity.ts +0 -234
  276. package/src/apps-shell.ts +0 -370
  277. package/src/auth/bearer.ts +0 -61
  278. package/src/auth/clerk.ts +0 -553
  279. package/src/auth/downstream-oauth.ts +0 -575
  280. package/src/call-admission.ts +0 -521
  281. package/src/catalog-fingerprint.ts +0 -52
  282. package/src/catalog-limits.ts +0 -11
  283. package/src/catalog-service.ts +0 -1153
  284. package/src/catalog.ts +0 -790
  285. package/src/concurrency.ts +0 -39
  286. package/src/connector-scope.ts +0 -66
  287. package/src/connectors/api.ts +0 -159
  288. package/src/connectors/remote-mcp.ts +0 -1090
  289. package/src/credentials.ts +0 -428
  290. package/src/errors.ts +0 -364
  291. package/src/execute.ts +0 -1406
  292. package/src/executor-admission.ts +0 -328
  293. package/src/executor-result.ts +0 -107
  294. package/src/executors/quickjs-child.ts +0 -172
  295. package/src/executors/quickjs-protocol.ts +0 -99
  296. package/src/executors/quickjs-runtime.ts +0 -470
  297. package/src/executors/quickjs.ts +0 -776
  298. package/src/favicon.ts +0 -53
  299. package/src/index.ts +0 -666
  300. package/src/invocation.ts +0 -655
  301. package/src/json-schema.ts +0 -11
  302. package/src/mcp-result.ts +0 -43
  303. package/src/meta-tools.ts +0 -1607
  304. package/src/node.ts +0 -206
  305. package/src/operator-ui/browser.css +0 -423
  306. package/src/operator-ui/browser.ts +0 -1281
  307. package/src/operator-ui/generated.ts +0 -4
  308. package/src/operator-ui/model.ts +0 -111
  309. package/src/providers/cloudflare.ts +0 -3803
  310. package/src/providers/linear.ts +0 -301
  311. package/src/providers/mixpanel.ts +0 -228
  312. package/src/providers/notion.ts +0 -1879
  313. package/src/providers/stripe.ts +0 -306
  314. package/src/registry.ts +0 -1085
  315. package/src/retired-toolkits.ts +0 -14
  316. package/src/routes/access-tokens.ts +0 -115
  317. package/src/routes/activity.ts +0 -215
  318. package/src/routes/credentials.ts +0 -254
  319. package/src/routes/mcp.ts +0 -429
  320. package/src/routes/oauth.ts +0 -322
  321. package/src/routes/shared.ts +0 -247
  322. package/src/routes/ui.ts +0 -152
  323. package/src/server.ts +0 -195
  324. package/src/skills.ts +0 -271
  325. package/src/storage/file.ts +0 -123
  326. package/src/storage/memory.ts +0 -44
  327. package/src/timeout.ts +0 -84
  328. package/src/tool-safety.ts +0 -15
  329. package/src/types.ts +0 -523
  330. package/src/ui.ts +0 -744
  331. package/src/validate.ts +0 -387
  332. package/src/version.ts +0 -7
package/bin/connecta.mjs CHANGED
@@ -64,7 +64,7 @@ async function init() {
64
64
  // Restore both conventions explicitly in the generated project.
65
65
  await writeFile(
66
66
  join(stage, ".gitignore"),
67
- ".connecta-state.json\n.env\nnode_modules/\n",
67
+ ".connecta-state.json\n.connecta-activity.jsonl\n.env\nnode_modules/\n",
68
68
  );
69
69
  await rm(join(stage, "CLAUDE.md"), { force: true });
70
70
  try {
@@ -186,6 +186,20 @@ async function doctor() {
186
186
  if (health.status !== "ok") {
187
187
  throw new Error(`Unexpected health status: ${String(health.status)}`);
188
188
  }
189
+ // Whatever ran the program is what doctor names. The deployment reports its
190
+ // own sandbox and a deployment that reports none gets an executor-neutral
191
+ // line — anything else is doctor asserting a sandbox it never saw (#368).
192
+ // The string arrives from a server and lands in a terminal, so it is
193
+ // sanitized here too rather than trusted twice.
194
+ const executorName =
195
+ typeof health.executor?.name === "string"
196
+ ? health.executor.name
197
+ .replace(/[^\w .+-]+/g, " ")
198
+ .replace(/\s+/g, " ")
199
+ .trim()
200
+ .slice(0, 40)
201
+ .trim()
202
+ : "";
189
203
  let requestId = 0;
190
204
  const mcp = async (method, params) =>
191
205
  jsonResponse(
@@ -240,13 +254,40 @@ async function doctor() {
240
254
  JSON.parse(executed.result?.content?.[0]?.text ?? "null");
241
255
  if (executed.result?.isError || executionResult?.result !== 42) {
242
256
  throw new Error(
243
- `QuickJS execution check failed: ${JSON.stringify(executed.result)}`,
257
+ `${executorName ? `${executorName} execution` : "execute_code"} check ` +
258
+ `failed: ${JSON.stringify(executed.result)}`,
259
+ );
260
+ }
261
+
262
+ // Drift is reported, never failed on. A downstream that grew a tool nobody
263
+ // has classified is a maintainer's next task, not a broken deployment: the
264
+ // unclassified tool already fails closed onto call_destructive_tool. These
265
+ // counts come from refreshes the deployment already served, so a deployment
266
+ // that has answered no catalog request yet reports nothing here (#343).
267
+ const drifted = Object.entries(health.catalogDrift ?? {}).filter(
268
+ ([, report]) =>
269
+ report.unclassifiedTools ||
270
+ report.unservedTools ||
271
+ report.annotationConflicts ||
272
+ report.schemaChanges,
273
+ );
274
+ for (const [connectorId, report] of drifted) {
275
+ console.warn(
276
+ `[connecta] catalog drift on "${connectorId}" (observed ` +
277
+ `${report.observedAt}): ${report.unclassifiedTools} unclassified, ` +
278
+ `${report.unservedTools} no longer served, ` +
279
+ `${report.annotationConflicts} annotation conflict(s), ` +
280
+ `${report.schemaChanges} schema change(s).`,
244
281
  );
245
282
  }
246
283
 
247
284
  console.log(
248
285
  `Connecta doctor passed: ${health.connectors} connector(s), ` +
249
- "QuickJS executed, prescribed seven-tool surface.",
286
+ `${executorName ? `${executorName} executed` : "code executed"}, ` +
287
+ "prescribed seven-tool surface" +
288
+ (drifted.length > 0
289
+ ? `, catalog drift on ${drifted.length} connector(s).`
290
+ : "."),
250
291
  );
251
292
  }
252
293
 
@@ -29,4 +29,3 @@ export declare class AccessTokenManager {
29
29
  revoke(id: string, revokedBy: string): Promise<AccessTokenMetadata | null>;
30
30
  private authorize;
31
31
  }
32
- //# sourceMappingURL=access-tokens.d.ts.map
@@ -222,4 +222,3 @@ export class AccessTokenManager {
222
222
  return { ok: true, subjectId: record.id };
223
223
  }
224
224
  }
225
- //# sourceMappingURL=access-tokens.js.map
@@ -1,4 +1,4 @@
1
- import type { Logger } from "./types.js";
1
+ import type { CatalogDriftCounts, Logger } from "./types.js";
2
2
  export type ActivityCallSource = "call_tool" | "call_destructive_tool" | "batch_call" | "execute_code";
3
3
  export type ActivityOutcome = "success" | "error" | "timeout" | "cancelled";
4
4
  export type AgentFriction = "tool_not_found" | "schema_retry" | "destructive_reroute" | "auth_required" | "result_too_large";
@@ -51,6 +51,26 @@ export interface ToolCallActivityEvent {
51
51
  serverVersion: string;
52
52
  deploymentId?: string;
53
53
  }
54
+ /**
55
+ * A downstream catalog moved away from the manifest a release reviewed.
56
+ *
57
+ * Four integers and an id: the type has nowhere to put a tool name, a schema,
58
+ * an argument, a result, or a downstream error string, which is the same
59
+ * construction guarantee `ToolCallActivityEvent` makes. Which tool drifted is
60
+ * deliberately absent — the runtime reports that something did, and the
61
+ * maintainer-run check with a live catalog in front of it names names
62
+ * ([#343](https://github.com/zackbart/connecta/issues/343),
63
+ * [#351](https://github.com/zackbart/connecta/issues/351)).
64
+ */
65
+ export interface CatalogDriftActivityEvent extends CatalogDriftCounts {
66
+ schemaVersion: 1;
67
+ id: string;
68
+ occurredAt: string;
69
+ connectorId: string;
70
+ serverName: string;
71
+ serverVersion: string;
72
+ deploymentId?: string;
73
+ }
54
74
  export interface ActivityPage {
55
75
  events: ToolCallActivityEvent[];
56
76
  nextCursor?: string;
@@ -69,6 +89,13 @@ export interface ActivityReadPage {
69
89
  /** Write-only deployments can implement only this small, vendor-neutral seam. */
70
90
  export interface ActivitySink {
71
91
  record(event: ToolCallActivityEvent): void | Promise<void>;
92
+ /**
93
+ * Optional catalog-drift channel. Optional rather than a widened `record`,
94
+ * because a store written before drift existed already decided what a row
95
+ * looks like: a sink that does not implement this simply never hears about
96
+ * drift, and the operator still reads the same counts from connector status.
97
+ */
98
+ recordCatalogDrift?(event: CatalogDriftActivityEvent): void | Promise<void>;
72
99
  }
73
100
  /** Optional read side used by Connecta's authenticated Activity UI. */
74
101
  export interface ActivityReader {
@@ -86,6 +113,20 @@ export declare class InvalidActivityCursorError extends Error {
86
113
  constructor();
87
114
  }
88
115
  export type ActivityReadGate = (actor: ActivityActor) => boolean | Promise<boolean>;
116
+ /**
117
+ * Deployment-scoped destination for drift observations. Not request-scoped:
118
+ * a drifting catalog is something the deployment saw, not something a caller
119
+ * did, so it carries no actor and no request id to attribute it to one.
120
+ */
121
+ export interface CatalogDriftActivityContext {
122
+ sink: ActivitySink;
123
+ serverInfo: {
124
+ name: string;
125
+ version: string;
126
+ };
127
+ deploymentId?: string;
128
+ logger: Logger;
129
+ }
89
130
  /** Request-scoped context shared by direct, batch, and code-mode call paths. */
90
131
  export interface ActivityRequestContext {
91
132
  sink: ActivitySink;
@@ -106,4 +147,11 @@ export type ActivityEventInput = Pick<ToolCallActivityEvent, "connectorId" | "to
106
147
  * Engine complete inline; Node promises remain detached from the response.
107
148
  */
108
149
  export declare function recordToolActivity(context: ActivityRequestContext | undefined, input: ActivityEventInput): void;
109
- //# sourceMappingURL=activity.d.ts.map
150
+ /**
151
+ * Record one catalog-drift observation, best-effort like every other activity
152
+ * write: a store that is down, or a sink that never implemented the channel,
153
+ * can never change what a refresh returned to the caller who paid for it.
154
+ */
155
+ export declare function recordCatalogDriftActivity(context: CatalogDriftActivityContext | undefined, input: {
156
+ connectorId: string;
157
+ } & CatalogDriftCounts): void;
package/dist/activity.js CHANGED
@@ -89,4 +89,37 @@ export function recordToolActivity(context, input) {
89
89
  context.logger.warn("[connecta] activity record failed", error);
90
90
  }
91
91
  }
92
- //# sourceMappingURL=activity.js.map
92
+ /**
93
+ * Record one catalog-drift observation, best-effort like every other activity
94
+ * write: a store that is down, or a sink that never implemented the channel,
95
+ * can never change what a refresh returned to the caller who paid for it.
96
+ */
97
+ export function recordCatalogDriftActivity(context, input) {
98
+ if (!context?.sink.recordCatalogDrift)
99
+ return;
100
+ const event = {
101
+ schemaVersion: 1,
102
+ id: crypto.randomUUID(),
103
+ occurredAt: new Date().toISOString(),
104
+ connectorId: boundedEchoText(input.connectorId, MAX_ACTIVITY_NAME_BYTES),
105
+ unclassifiedTools: input.unclassifiedTools,
106
+ unservedTools: input.unservedTools,
107
+ annotationConflicts: input.annotationConflicts,
108
+ schemaChanges: input.schemaChanges,
109
+ serverName: context.serverInfo.name,
110
+ serverVersion: context.serverInfo.version,
111
+ ...(context.deploymentId ? { deploymentId: context.deploymentId } : {}),
112
+ };
113
+ try {
114
+ const result = context.sink.recordCatalogDrift(event);
115
+ if (!result || typeof result.then !== "function") {
116
+ return;
117
+ }
118
+ void Promise.resolve(result).catch((error) => {
119
+ context.logger.warn("[connecta] catalog drift record failed", error);
120
+ });
121
+ }
122
+ catch (error) {
123
+ context.logger.warn("[connecta] catalog drift record failed", error);
124
+ }
125
+ }
@@ -37,4 +37,3 @@ export declare const MCP_APPS_EXTENSION = "io.modelcontextprotocol/ui";
37
37
  * no direct network.
38
38
  */
39
39
  export declare const PROGRAM_UI_SHELL_HTML = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>connecta program view</title>\n <style>\n html,\n body {\n margin: 0;\n padding: 0;\n background: transparent;\n }\n #program-view {\n display: block;\n width: 100%;\n min-height: 480px;\n border: 0;\n }\n </style>\n </head>\n <body>\n <iframe\n id=\"program-view\"\n title=\"Program-rendered view\"\n sandbox=\"allow-scripts\"\n srcdoc=\"\"\n ></iframe>\n <script>\n (function () {\n \"use strict\";\n // The outer shell is the only host peer. The payload frame is\n // sandboxed to scripts alone, with no same-origin escape. Its one\n // optional message dialect is handled below and translated into\n // bounded call_tool requests; raw JSON-RPC is never forwarded.\n var host = window.parent;\n var view = document.getElementById(\"program-view\");\n var initializeId = \"connecta-ui-initialize\";\n var lastWidth = 0;\n var lastHeight = 0;\n var reads = null;\n var hostCanCallTools = false;\n var nextHostRequestId = 0;\n var pendingHostReads = Object.create(null);\n var activeHostReads = 0;\n var maxActiveHostReads = 8;\n\n function send(message) {\n if (!host || host === window) return;\n host.postMessage(message, \"*\");\n }\n\n function notify(method, params) {\n send({ jsonrpc: \"2.0\", method: method, params: params });\n }\n\n // Program views are fixed-height by construction. The shell has no\n // content-height bridge to the payload frame, so it can never learn\n // the payload's content height, and\n // what it reports here is its own box: the min-height above, unless\n // the host has given it more. Taller content scrolls inside the inner\n // frame rather than growing the view. Raising the min-height is the\n // only lever; a content-height signal would cost the isolation.\n function reportSize() {\n var width = Math.ceil(document.documentElement.clientWidth);\n var height = Math.ceil(document.documentElement.scrollHeight);\n if (width === lastWidth && height === lastHeight) return;\n lastWidth = width;\n lastHeight = height;\n notify(\"ui/notifications/size-changed\", {\n width: width,\n height: height\n });\n }\n\n function payload(result) {\n if (!result || typeof result !== \"object\") return null;\n var meta = result._meta;\n if (!meta || typeof meta !== \"object\") return null;\n var value = meta[\"connecta/ui\"];\n if (!value || typeof value !== \"object\") return null;\n return typeof value.html === \"string\" && value.html.length > 0\n ? value\n : null;\n }\n\n // This function is serialized into the opaque-origin payload frame.\n // It knows no addresses and has no host channel of its own: one named\n // read request goes to the trusted outer shell and one correlated\n // result comes back.\n function payloadReadBridge() {\n \"use strict\";\n var pending = Object.create(null);\n var nextId = 0;\n\n function read(name, args) {\n return new Promise(function (resolve, reject) {\n var id = String(++nextId);\n pending[id] = { resolve: resolve, reject: reject };\n try {\n window.parent.postMessage({\n type: \"connecta/read\",\n id: id,\n name: name,\n args: args === undefined ? {} : args\n }, \"*\");\n } catch (error) {\n delete pending[id];\n reject(error);\n }\n });\n }\n\n Object.defineProperty(globalThis, \"connecta\", {\n value: Object.freeze({ read: read }),\n configurable: false,\n enumerable: true,\n writable: false\n });\n\n window.addEventListener(\"message\", function (event) {\n if (event.source !== window.parent) return;\n var message = event.data;\n if (!message || message.type !== \"connecta/read-result\") return;\n var waiter = pending[message.id];\n if (!waiter) return;\n delete pending[message.id];\n if (message.ok) waiter.resolve(message.value);\n else waiter.reject(new Error(message.error || \"Read failed\"));\n });\n }\n\n function htmlWithReadBridge(html) {\n var script =\n \"<scr\" + \"ipt>(\" + payloadReadBridge.toString() + \")();</scr\" + \"ipt>\";\n var head = /<head(?:\\s[^>]*)?>/i.exec(html);\n if (head) {\n var at = (head.index || 0) + head[0].length;\n return html.slice(0, at) + script + html.slice(at);\n }\n var document = /<html(?:\\s[^>]*)?>/i.exec(html);\n if (document) {\n var afterHtml = (document.index || 0) + document[0].length;\n return html.slice(0, afterHtml) + \"<head>\" + script + \"</head>\" + html.slice(afterHtml);\n }\n return script + html;\n }\n\n function render(params) {\n var value =\n payload(params) ||\n payload(params && params.result) ||\n payload(params && params.toolResult);\n if (value === null) return;\n reads = value.reads && typeof value.reads === \"object\"\n ? value.reads\n : null;\n view.srcdoc = reads\n ? htmlWithReadBridge(value.html)\n : value.html;\n reportSize();\n }\n\n function readError(message, fallback) {\n if (message && typeof message.message === \"string\") return message.message;\n if (message && message.data && typeof message.data.message === \"string\") {\n return message.data.message;\n }\n return fallback;\n }\n\n function finishInnerRead(innerId, ok, value) {\n if (!view.contentWindow) return;\n view.contentWindow.postMessage(ok\n ? { type: \"connecta/read-result\", id: innerId, ok: true, value: value }\n : { type: \"connecta/read-result\", id: innerId, ok: false, error: value }, \"*\");\n }\n\n function beginInnerRead(message) {\n if (!hostCanCallTools) {\n finishInnerRead(message && message.id, false, \"This host does not support app-initiated server tool calls\");\n return;\n }\n if (!message || typeof message.id !== \"string\" || typeof message.name !== \"string\") return;\n if (!reads || !Object.prototype.hasOwnProperty.call(reads, message.name)) {\n finishInnerRead(message.id, false, \"Unknown read binding\");\n return;\n }\n if (activeHostReads >= maxActiveHostReads) {\n finishInnerRead(message.id, false, \"Too many concurrent reads\");\n return;\n }\n var binding = reads[message.name];\n if (!binding || typeof binding !== \"object\" || typeof binding.address !== \"string\") {\n finishInnerRead(message.id, false, \"Invalid read binding\");\n return;\n }\n var supplied = message.args;\n if (!supplied || typeof supplied !== \"object\" || Array.isArray(supplied)) {\n finishInnerRead(message.id, false, \"Read arguments must be an object\");\n return;\n }\n var allowed = Array.isArray(binding.viewArgs) ? binding.viewArgs : [];\n var suppliedKeys = Object.keys(supplied);\n for (var i = 0; i < suppliedKeys.length; i++) {\n var key = suppliedKeys[i];\n if (allowed.indexOf(key) === -1) {\n finishInnerRead(message.id, false, \"Undeclared read argument \" + JSON.stringify(key));\n return;\n }\n }\n var args = Object.create(null);\n var fixed = binding.fixedArgs && typeof binding.fixedArgs === \"object\"\n ? binding.fixedArgs\n : {};\n Object.keys(fixed).forEach(function (key) { args[key] = fixed[key]; });\n suppliedKeys.forEach(function (key) { args[key] = supplied[key]; });\n\n var hostId = \"connecta-ui-read-\" + String(++nextHostRequestId);\n pendingHostReads[hostId] = { innerId: message.id };\n activeHostReads++;\n send({\n jsonrpc: \"2.0\",\n id: hostId,\n method: \"tools/call\",\n params: {\n name: \"call_tool\",\n arguments: {\n address: binding.address,\n args: args,\n resultMode: \"value\"\n }\n }\n });\n }\n\n function finishHostRead(message) {\n var pending = pendingHostReads[message.id];\n if (!pending) return false;\n delete pendingHostReads[message.id];\n activeHostReads--;\n if (message.error) {\n finishInnerRead(pending.innerId, false, readError(message.error, \"Host rejected read\"));\n return true;\n }\n var toolResult = message.result;\n if (!toolResult || typeof toolResult !== \"object\") {\n finishInnerRead(pending.innerId, false, \"Host returned an invalid tool result\");\n return true;\n }\n var structured = toolResult.structuredContent;\n if (toolResult.isError || (structured && structured.ok === false)) {\n var detail = structured && structured.error;\n var content = Array.isArray(toolResult.content)\n ? toolResult.content.find(function (block) { return block && block.type === \"text\"; })\n : null;\n finishInnerRead(\n pending.innerId,\n false,\n readError(detail, content && content.text ? content.text : \"Read failed\")\n );\n return true;\n }\n var value = structured && structured.ok === true &&\n Object.prototype.hasOwnProperty.call(structured, \"data\")\n ? structured.data\n : structured !== undefined\n ? structured\n : toolResult;\n finishInnerRead(pending.innerId, true, value);\n return true;\n }\n\n window.addEventListener(\"message\", function (event) {\n var message = event.data;\n if (event.source === view.contentWindow) {\n if (message && message.type === \"connecta/read\") beginInnerRead(message);\n return;\n }\n if (event.source !== host) return;\n if (!message || message.jsonrpc !== \"2.0\") return;\n if (message.id !== undefined && finishHostRead(message)) return;\n if (message.method === \"ui/notifications/tool-result\") {\n render(message.params);\n return;\n }\n if (message.method === \"ui/resource-teardown\") {\n // A host->view request, not a notification: the host waits for\n // this reply before it tears the view down. There is nothing to\n // release, so answer immediately rather than make it time out.\n if (message.id !== undefined && message.id !== null) {\n send({ jsonrpc: \"2.0\", id: message.id, result: {} });\n }\n return;\n }\n // Only a completed handshake earns \"initialized\". A JSON-RPC error\n // response carries the same id, and announcing initialization on one\n // would assert a handshake that never happened.\n if (message.id === initializeId && message.result !== undefined) {\n var capabilities = message.result.hostCapabilities;\n hostCanCallTools = Boolean(capabilities && capabilities.serverTools);\n notify(\"ui/notifications/initialized\", {});\n }\n });\n\n window.addEventListener(\"resize\", reportSize);\n view.addEventListener(\"load\", reportSize);\n\n // Every field here is required by the Apps initialize schema, and a\n // conforming host rejects the request outright when one is missing \u2014\n // which would strand the shell before any tool result arrives.\n send({\n jsonrpc: \"2.0\",\n id: initializeId,\n method: \"ui/initialize\",\n params: {\n appInfo: { name: \"connecta program view\", version: \"1\" },\n appCapabilities: {},\n protocolVersion: \"2026-01-26\"\n }\n });\n reportSize();\n })();\n </script>\n </body>\n</html>\n";
40
- //# sourceMappingURL=apps-shell.d.ts.map
@@ -363,4 +363,3 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
363
363
  </body>
364
364
  </html>
365
365
  `;
366
- //# sourceMappingURL=apps-shell.js.map
@@ -9,4 +9,3 @@ export interface BearerTokenOptions {
9
9
  * falls through so a co-configured Clerk provider can still admit the request.
10
10
  */
11
11
  export declare function bearerToken(secret: string, options?: BearerTokenOptions): InboundAuth;
12
- //# sourceMappingURL=bearer.d.ts.map
@@ -46,4 +46,3 @@ export function bearerToken(secret, options = {}) {
46
46
  },
47
47
  };
48
48
  }
49
- //# sourceMappingURL=bearer.js.map
@@ -34,4 +34,3 @@ export interface ClerkAuthOptions {
34
34
  */
35
35
  export declare function clerkAuth(opts: ClerkAuthOptions): InboundAuth;
36
36
  export {};
37
- //# sourceMappingURL=clerk.d.ts.map
@@ -20,11 +20,45 @@ function tokenShape(request) {
20
20
  return "jwt";
21
21
  return "other";
22
22
  }
23
- /** Clerk Frontend API origin, derived from pk_(test|live)_<b64 domain>. */
23
+ /**
24
+ * Clerk Frontend API origin, derived from pk_(test|live)_<b64 domain>.
25
+ *
26
+ * The key is operator config, and a key that cannot yield an origin is a
27
+ * structural mistake: it throws here, at construction, in the same voice as the
28
+ * `allowedDomains` checks. Left unvalidated, `atob` raises a bare
29
+ * `InvalidCharacterError` from inside the returned object — which, on a
30
+ * deployment that builds per request (the Workers shape), turns every route
31
+ * including `/health` into a 500 with a stack that names base64 rather than the
32
+ * environment variable the operator has to fix.
33
+ *
34
+ * The rejected value is never quoted back. A publishable key is public, but the
35
+ * commonest way to land here is pasting the *secret* key into the publishable
36
+ * slot, and a startup error is a log line.
37
+ */
24
38
  function fapiUrl(publishableKey) {
25
- const key = publishableKey.replace(/^pk_(test|live)_/, "");
26
- const decoded = atob(key).replace(/\$$/, "");
27
- return `https://${decoded}`;
39
+ const shape = " Copy the publishable key from the Clerk dashboard: `pk_test_` or " +
40
+ "`pk_live_` followed by the base64-encoded Frontend API domain.";
41
+ if (typeof publishableKey !== "string" || publishableKey === "") {
42
+ throw new Error("clerkAuth: `publishableKey` is missing or not a string." + shape);
43
+ }
44
+ const encoded = /^pk_(?:test|live)_([A-Za-z0-9+/=]+)$/.exec(publishableKey)?.[1];
45
+ if (encoded === undefined) {
46
+ throw new Error("clerkAuth: `publishableKey` is not a Clerk publishable key." + shape);
47
+ }
48
+ let decoded;
49
+ try {
50
+ decoded = atob(encoded);
51
+ }
52
+ catch {
53
+ throw new Error("clerkAuth: `publishableKey` does not carry decodable base64." + shape);
54
+ }
55
+ // Clerk terminates the encoded domain with `$`; everything else is the host.
56
+ const domain = decoded.replace(/\$$/, "");
57
+ if (!isDomain(domain)) {
58
+ throw new Error("clerkAuth: `publishableKey` does not decode to a Frontend API domain." +
59
+ shape);
60
+ }
61
+ return `https://${domain}`;
28
62
  }
29
63
  const GATE_ALLOWED_TTL_MS = 60 * 1000;
30
64
  const GATE_FORBIDDEN_TTL_MS = 30 * 1000;
@@ -175,6 +209,9 @@ function emailDomain(email) {
175
209
  */
176
210
  export function clerkAuth(opts) {
177
211
  assertNoRetiredToolkitOptions("clerkAuth", opts);
212
+ // Before the Clerk client, so a malformed key fails as a connecta
213
+ // configuration error rather than however the SDK happens to treat it.
214
+ const frontendApiUrl = fapiUrl(opts.publishableKey);
178
215
  const clerk = createClerkClient({
179
216
  secretKey: opts.secretKey,
180
217
  publishableKey: opts.publishableKey,
@@ -329,12 +366,12 @@ export function clerkAuth(opts) {
329
366
  };
330
367
  return {
331
368
  kind: "clerk",
332
- activityActorNamespace: fapiUrl(opts.publishableKey),
369
+ activityActorNamespace: frontendApiUrl,
333
370
  activityActorLabel: resolveActivityLabel,
334
371
  uiAuth: {
335
372
  kind: "clerk",
336
373
  publishableKey: opts.publishableKey,
337
- frontendApiUrl: fapiUrl(opts.publishableKey),
374
+ frontendApiUrl,
338
375
  ...(opts.signInUrl ? { signInUrl: opts.signInUrl } : {}),
339
376
  ...(opts.signUpUrl ? { signUpUrl: opts.signUpUrl } : {}),
340
377
  },
@@ -350,14 +387,14 @@ export function clerkAuth(opts) {
350
387
  pathname === "/.well-known/oauth-protected-resource/mcp") {
351
388
  return Response.json({
352
389
  resource: `${base}/mcp`,
353
- authorization_servers: [fapiUrl(opts.publishableKey)],
390
+ authorization_servers: [frontendApiUrl],
354
391
  bearer_methods_supported: ["header"],
355
392
  scopes_supported: scopes,
356
393
  }, { headers: CORS_HEADERS });
357
394
  }
358
395
  if (pathname === "/.well-known/oauth-authorization-server") {
359
396
  try {
360
- const upstream = await fetch(`${fapiUrl(opts.publishableKey)}/.well-known/oauth-authorization-server`);
397
+ const upstream = await fetch(`${frontendApiUrl}/.well-known/oauth-authorization-server`);
361
398
  if (!upstream.ok) {
362
399
  return Response.json({ error: "upstream authorization server metadata unavailable" }, { status: 502, headers: CORS_HEADERS });
363
400
  }
@@ -426,4 +463,3 @@ export function clerkAuth(opts) {
426
463
  },
427
464
  };
428
465
  }
429
- //# sourceMappingURL=clerk.js.map
@@ -121,4 +121,3 @@ export declare class KvOAuthProvider implements OAuthClientProvider {
121
121
  resetAuthorization(operatorDisconnected?: boolean): Promise<void>;
122
122
  invalidateCredentials(scope: "all" | "client" | "tokens" | "verifier" | "discovery"): Promise<void>;
123
123
  }
124
- //# sourceMappingURL=downstream-oauth.d.ts.map
@@ -434,4 +434,3 @@ export class KvOAuthProvider {
434
434
  }
435
435
  }
436
436
  }
437
- //# sourceMappingURL=downstream-oauth.js.map
@@ -78,4 +78,3 @@ export declare class ConnectorCallAdmissionController {
78
78
  private budgetLimited;
79
79
  private cancelled;
80
80
  }
81
- //# sourceMappingURL=call-admission.d.ts.map
@@ -341,4 +341,3 @@ export class ConnectorCallAdmissionController {
341
341
  return new CallAdmissionError("cancelled", "timeout", `Connector "${this.connectorId}" call was cancelled before admission.`, { cause: signal.reason });
342
342
  }
343
343
  }
344
- //# sourceMappingURL=call-admission.js.map
@@ -0,0 +1,99 @@
1
+ import type { CatalogDriftCounts, CatalogDriftReport, Connector, ToolDef } from "./types.js";
2
+ /**
3
+ * Rebuild a drift report as four counts and a bounded timestamp.
4
+ *
5
+ * `Connector.catalogDrift()` sits on the open plugin seam, and what it returns
6
+ * lands in the body of unauthenticated `/health` and on connector status.
7
+ * TypeScript constrains neither an extra enumerable property nor the length of
8
+ * `observedAt` at runtime, so "counts and nothing else" is *made* true here —
9
+ * at the boundary where third-party output becomes a response — rather than
10
+ * trusted. The activity path reconstructs its five fields for the same reason.
11
+ */
12
+ export declare function boundedCatalogDrift(report: CatalogDriftReport | undefined): CatalogDriftReport | undefined;
13
+ /**
14
+ * What a release decided a tool does. `"read-only"` is observational;
15
+ * `"destructive"` modifies or removes state that already exists; `"additive"`
16
+ * only brings something new into being. Both writes leave the read-only path
17
+ * — the distinction decides whether the connection asserts `destructiveHint`,
18
+ * which shapes the approval copy a human reads.
19
+ */
20
+ type VettedVerdict = "read-only" | "additive" | "destructive";
21
+ /** One tool as a release reviewed it. */
22
+ interface VettedToolRecord {
23
+ verdict: VettedVerdict;
24
+ /**
25
+ * Digest of the input and output schemas that release read, or undefined
26
+ * when no release has recorded them. Undefined is not "unchanged": a
27
+ * manifest with no digest cannot report a schema change, and says so by
28
+ * counting none. `npm run drift:check -- --record` reads them from a live
29
+ * catalog and prints the block a release pastes in
30
+ * ([#351](https://github.com/zackbart/connecta/issues/351)).
31
+ */
32
+ schemaDigest?: string;
33
+ }
34
+ /**
35
+ * The vetted manifest one hosted-MCP proxy ships: every tool name a release
36
+ * reviewed, what it reviewed it as, and — where a release recorded them — the
37
+ * schemas it read. This is the single per-provider source P13 asks for, so the
38
+ * classification the connector applies and the classification a drift check
39
+ * compares against cannot disagree.
40
+ */
41
+ export interface VettedCatalog {
42
+ /** Manifest format. Bumped when the comparison itself changes shape. */
43
+ version: 1;
44
+ tools: ReadonlyMap<string, VettedToolRecord>;
45
+ }
46
+ export interface VettedCatalogInput {
47
+ /** Names whose contract is observational rather than mutating. */
48
+ reads: ReadonlySet<string>;
49
+ /** Names that write, with the verdict that shapes the approval copy. */
50
+ writes: ReadonlyMap<string, "additive" | "destructive">;
51
+ /**
52
+ * Schemas this release reviewed, as `name` → digest from
53
+ * {@link vettedSchemaDigest}. Omit entirely until a release has actually
54
+ * read them; an invented digest reports drift that never happened.
55
+ */
56
+ schemaDigests?: Readonly<Record<string, string>>;
57
+ }
58
+ /**
59
+ * Digest the schemas of one downstream tool.
60
+ *
61
+ * Canonical rather than literal, unlike the catalog fingerprint in
62
+ * `src/catalog-fingerprint.ts`: that one is deliberately conservative because a
63
+ * spurious cache write is cheap, while a spurious drift finding spends a
64
+ * maintainer's attention on a downstream that reordered its JSON keys.
65
+ * Description and annotations are excluded — a reworded description is P1's
66
+ * business, and an annotation change is already its own drift category.
67
+ */
68
+ export declare function vettedSchemaDigest(tool: ToolDef): Promise<string>;
69
+ /**
70
+ * Build a provider's manifest from the lists it already maintains.
71
+ *
72
+ * Throws when a name is classified twice, because a tool that is both a read
73
+ * and a write is a review mistake that must not boot: the annotation the
74
+ * connector would apply depends on which branch runs first, and "safe by
75
+ * default" cannot be decided by ordering.
76
+ */
77
+ export declare function vettedCatalog(input: VettedCatalogInput): VettedCatalog;
78
+ /**
79
+ * Compare a live catalog with the manifest and count what moved.
80
+ *
81
+ * Counts only, and by construction: there is nowhere here to put a tool name,
82
+ * a schema, or a downstream string, so no later surface has to remember to
83
+ * strip one. Reads nothing but the tools it was handed — the caller already
84
+ * fetched them to serve a request, and this function never fetches anything.
85
+ */
86
+ export declare function detectCatalogDrift(catalog: VettedCatalog, tools: readonly ToolDef[]): Promise<CatalogDriftCounts>;
87
+ /**
88
+ * Wrap a hosted-MCP connector in its vetted manifest: the classification the
89
+ * catalog is normalized with, and the drift check that rides the same listing.
90
+ *
91
+ * The check happens where the tools are already in hand and still unmodified —
92
+ * after the downstream answered, before the classification is applied. It adds
93
+ * no request of its own, which is the whole boundary: connecta watches a
94
+ * contract while it is serving a refresh the deployment asked for, and never
95
+ * initiates one to go looking ([#179](https://github.com/zackbart/connecta/issues/179),
96
+ * [#343](https://github.com/zackbart/connecta/issues/343)).
97
+ */
98
+ export declare function withVettedCatalog(connector: Connector, catalog: VettedCatalog): Connector;
99
+ export {};