@zackbart/connecta 0.28.1 → 0.30.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/CHANGELOG.md +855 -5
  2. package/README.md +125 -116
  3. package/bin/connecta.mjs +121 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/argument-redaction.js +230 -0
  12. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  13. package/dist/auth/clerk-sdk/client.js +5 -0
  14. package/dist/auth/clerk-sdk/generated.js +1845 -0
  15. package/dist/auth/clerk-transport.js +19 -0
  16. package/dist/auth/clerk.d.ts +17 -7
  17. package/dist/auth/clerk.js +189 -107
  18. package/dist/auth/cloudflare-access.js +25 -26
  19. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  20. package/dist/auth/downstream-client-metadata.js +133 -0
  21. package/dist/auth/downstream-oauth.js +1584 -1580
  22. package/dist/auth/downstream-revocation.js +106 -0
  23. package/dist/auth/oauth-token-response.js +231 -0
  24. package/dist/auth/oauth-v2-migration.js +116 -0
  25. package/dist/auth/static-oauth.js +93 -71
  26. package/dist/auth-elicitation.js +288 -0
  27. package/dist/authorization-handoff.js +111 -0
  28. package/dist/branding.d.ts +8 -16
  29. package/dist/branding.js +26 -59
  30. package/dist/byte-read-response.js +39 -0
  31. package/dist/call-admission.js +12 -16
  32. package/dist/catalog-cache.js +739 -0
  33. package/dist/catalog-drift.d.ts +50 -15
  34. package/dist/catalog-drift.js +365 -128
  35. package/dist/catalog-fingerprint.js +2 -4
  36. package/dist/catalog-freshness.js +13 -0
  37. package/dist/catalog-limits.js +2 -2
  38. package/dist/catalog-service.js +409 -225
  39. package/dist/catalog.js +43 -84
  40. package/dist/concurrency.js +3 -4
  41. package/dist/config-defaults.d.ts +39 -0
  42. package/dist/config-defaults.js +46 -0
  43. package/dist/config-schema.d.ts +181 -0
  44. package/dist/config-schema.js +435 -0
  45. package/dist/config-value-sources.js +102 -0
  46. package/dist/config.d.ts +633 -0
  47. package/dist/config.js +508 -0
  48. package/dist/connector-access.d.ts +1 -0
  49. package/dist/connector-access.js +13 -15
  50. package/dist/connector-caller.d.ts +8 -0
  51. package/dist/connector-caller.js +15 -6
  52. package/dist/connector-scope.js +1 -1
  53. package/dist/connectors/api-connector.d.ts +59 -7
  54. package/dist/connectors/api-connector.js +106 -19
  55. package/dist/connectors/api.js +4 -1
  56. package/dist/connectors/guarded-fetch.d.ts +144 -0
  57. package/dist/connectors/guarded-fetch.js +87 -38
  58. package/dist/connectors/negotiation-cache.js +46 -0
  59. package/dist/connectors/option-shapes.js +27 -0
  60. package/dist/connectors/remote-mcp.d.ts +30 -4
  61. package/dist/connectors/remote-mcp.js +1774 -320
  62. package/dist/connectors/resource-uri.js +335 -0
  63. package/dist/credential-contract.d.ts +10 -0
  64. package/dist/credential-rules.js +1 -3
  65. package/dist/credential-url.js +108 -0
  66. package/dist/credentials.d.ts +6 -1
  67. package/dist/credentials.js +27 -18
  68. package/dist/d1.d.ts +130 -0
  69. package/dist/d1.js +290 -0
  70. package/dist/describe-config.d.ts +165 -0
  71. package/dist/describe-config.js +315 -0
  72. package/dist/described.js +106 -0
  73. package/dist/downstream-input-context.js +128 -0
  74. package/dist/downstream-input.js +325 -0
  75. package/dist/errors.d.ts +132 -31
  76. package/dist/errors.js +175 -87
  77. package/dist/execute.js +798 -318
  78. package/dist/executor-admission.d.ts +1 -0
  79. package/dist/executor-admission.js +11 -15
  80. package/dist/executor-contract.d.ts +20 -0
  81. package/dist/executor-contract.js +48 -0
  82. package/dist/executor-result.js +30 -13
  83. package/dist/executors/quickjs-child.js +15 -9
  84. package/dist/executors/quickjs-protocol.js +5 -6
  85. package/dist/executors/quickjs-runtime.js +152 -59
  86. package/dist/executors/quickjs.js +49 -39
  87. package/dist/favicon.js +1 -2
  88. package/dist/fix-prompt.js +1 -3
  89. package/dist/guest-runtime.js +238 -0
  90. package/dist/guest-types.d.ts +218 -0
  91. package/dist/guest-types.js +1 -0
  92. package/dist/html-security.js +24 -0
  93. package/dist/identity.js +1 -3
  94. package/dist/inbound-credential.js +52 -0
  95. package/dist/index.d.ts +24 -256
  96. package/dist/index.js +65 -478
  97. package/dist/invocation-auth.js +69 -0
  98. package/dist/invocation.js +262 -79
  99. package/dist/json-schema.d.ts +1 -1
  100. package/dist/mcp-client-context.js +35 -0
  101. package/dist/mcp-result.js +11 -7
  102. package/dist/meta-output.js +61 -0
  103. package/dist/meta-tool-names.d.ts +2 -0
  104. package/dist/meta-tool-names.js +3 -0
  105. package/dist/meta-tools.js +419 -272
  106. package/dist/module-contracts.d.ts +8 -23
  107. package/dist/node.d.ts +0 -2
  108. package/dist/node.js +2 -2
  109. package/dist/oauth-callback-outcome.js +1 -3
  110. package/dist/oauth-handoff.js +194 -0
  111. package/dist/oauth-sealing.js +20 -0
  112. package/dist/operator-record.d.ts +137 -0
  113. package/dist/operator-record.js +420 -0
  114. package/dist/operator-ui/assets.js +31 -0
  115. package/dist/operator-ui/contract.d.ts +72 -0
  116. package/dist/operator-ui/contract.js +1 -0
  117. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  118. package/dist/operator-ui/generated.js +5 -4
  119. package/dist/operator-ui/model.d.ts +12 -87
  120. package/dist/operator-ui/model.js +2 -9
  121. package/dist/operator-ui/setup-commands.js +1 -3
  122. package/dist/operator-ui/view.d.ts +323 -0
  123. package/dist/operator-ui/view.js +62 -189
  124. package/dist/page-styles.js +2 -3
  125. package/dist/program-result.js +5 -0
  126. package/dist/program-source.js +14 -2
  127. package/dist/{exempt-writes.js → program-writes.js} +60 -19
  128. package/dist/provider.d.ts +142 -0
  129. package/dist/provider.js +187 -0
  130. package/dist/providers/_shared/google/result-size.js +57 -0
  131. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  132. package/dist/providers/_shared/google/service-account.js +500 -0
  133. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  134. package/dist/providers/_shared/google/workspace.js +518 -0
  135. package/dist/providers/_shared/request-token.js +83 -0
  136. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  137. package/dist/providers/basecamp/index.js +997 -0
  138. package/dist/providers/basecamp/skill.generated.js +9 -0
  139. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  140. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  141. package/dist/providers/breeze/skill.generated.js +9 -0
  142. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  143. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  144. package/dist/providers/ccb/skill.generated.js +9 -0
  145. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  146. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  147. package/dist/providers/cloudflare/skill.generated.js +10 -0
  148. package/dist/providers/docs/index.d.ts +54 -0
  149. package/dist/providers/docs/index.js +1237 -0
  150. package/dist/providers/docs/skill.generated.js +9 -0
  151. package/dist/providers/drive/index.d.ts +53 -0
  152. package/dist/providers/drive/index.js +1523 -0
  153. package/dist/providers/drive/skill.generated.js +9 -0
  154. package/dist/providers/forms/index.d.ts +54 -0
  155. package/dist/providers/forms/index.js +1217 -0
  156. package/dist/providers/forms/skill.generated.js +11 -0
  157. package/dist/providers/github/auth.d.ts +32 -0
  158. package/dist/providers/github/auth.js +361 -0
  159. package/dist/providers/github/hosted.js +218 -0
  160. package/dist/providers/github/index.d.ts +13 -0
  161. package/dist/providers/github/index.js +140 -0
  162. package/dist/providers/github/key.js +100 -0
  163. package/dist/providers/github/rest.js +321 -0
  164. package/dist/providers/github/scope.d.ts +29 -0
  165. package/dist/providers/github/scope.js +124 -0
  166. package/dist/providers/github/skill.generated.js +8 -0
  167. package/dist/providers/gmail/index.d.ts +51 -0
  168. package/dist/providers/gmail/index.js +1436 -0
  169. package/dist/providers/gmail/skill.generated.js +9 -0
  170. package/dist/providers/infisical/index.d.ts +7 -0
  171. package/dist/providers/infisical/index.js +713 -0
  172. package/dist/providers/infisical/skill.generated.js +8 -0
  173. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  174. package/dist/providers/linear/index.js +352 -0
  175. package/dist/providers/linear/skill.generated.js +8 -0
  176. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  177. package/dist/providers/mixpanel/index.js +416 -0
  178. package/dist/providers/mixpanel/skill.generated.js +9 -0
  179. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  180. package/dist/providers/{notion.js → notion/index.js} +226 -239
  181. package/dist/providers/notion/skill.generated.js +10 -0
  182. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  183. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  184. package/dist/providers/overflow/skill.generated.js +8 -0
  185. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  186. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  187. package/dist/providers/planning-center/skill.generated.js +10 -0
  188. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  189. package/dist/providers/revenuecat/index.js +557 -0
  190. package/dist/providers/revenuecat/skill.generated.js +11 -0
  191. package/dist/providers/sheets/index.d.ts +54 -0
  192. package/dist/providers/sheets/index.js +1237 -0
  193. package/dist/providers/sheets/skill.generated.js +10 -0
  194. package/dist/providers/slides/index.d.ts +53 -0
  195. package/dist/providers/slides/index.js +2645 -0
  196. package/dist/providers/slides/skill.generated.js +9 -0
  197. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  198. package/dist/providers/stripe/index.js +266 -0
  199. package/dist/providers/stripe/skill.generated.js +11 -0
  200. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  201. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  202. package/dist/providers/tithely/skill.generated.js +8 -0
  203. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  204. package/dist/providers/vercel/index.js +1038 -0
  205. package/dist/providers/vercel/mcp-ownership.js +212 -0
  206. package/dist/providers/vercel/skill.generated.js +11 -0
  207. package/dist/registry.d.ts +110 -171
  208. package/dist/registry.js +588 -927
  209. package/dist/request-state.js +21 -0
  210. package/dist/resource-read.js +103 -0
  211. package/dist/result-shapes.js +17 -49
  212. package/dist/retired-toolkits.js +1 -1
  213. package/dist/routes/access-tokens.js +3 -6
  214. package/dist/routes/activity-context.js +21 -0
  215. package/dist/routes/activity-disclosure.js +40 -0
  216. package/dist/routes/activity.js +59 -21
  217. package/dist/routes/actor-labels.js +3 -50
  218. package/dist/routes/connect.js +221 -0
  219. package/dist/routes/credentials.js +15 -27
  220. package/dist/routes/mcp.js +365 -130
  221. package/dist/routes/oauth-client-metadata.js +15 -0
  222. package/dist/routes/oauth-management.js +45 -107
  223. package/dist/routes/oauth.js +110 -57
  224. package/dist/routes/operator.js +3 -3
  225. package/dist/routes/shared.d.ts +25 -64
  226. package/dist/routes/shared.js +160 -80
  227. package/dist/routes/skills.js +30 -0
  228. package/dist/routes/ui-config.js +225 -0
  229. package/dist/routes/ui-data.js +40 -35
  230. package/dist/routes/ui.js +66 -56
  231. package/dist/runtime/admission.js +4 -2
  232. package/dist/runtime/call-admission.js +3 -1
  233. package/dist/runtime/connector-scope.js +1 -4
  234. package/dist/runtime/run.js +10 -39
  235. package/dist/runtime/services.js +12 -117
  236. package/dist/runtime/shared-read.js +1 -3
  237. package/dist/runtime/storage.js +8 -1
  238. package/dist/sent-secrets.d.ts +93 -0
  239. package/dist/sent-secrets.js +1013 -0
  240. package/dist/server.js +28 -71
  241. package/dist/skills.js +416 -174
  242. package/dist/sqlite.d.ts +48 -0
  243. package/dist/sqlite.js +181 -0
  244. package/dist/storage/keys.js +443 -0
  245. package/dist/storage/memory.js +16 -4
  246. package/dist/storage/sql.d.ts +66 -0
  247. package/dist/storage/sql.js +543 -0
  248. package/dist/timeout.js +4 -2
  249. package/dist/tool-name.js +17 -0
  250. package/dist/tool-safety.d.ts +9 -35
  251. package/dist/tool-safety.js +20 -40
  252. package/dist/types.d.ts +336 -56
  253. package/dist/types.js +0 -1
  254. package/dist/ui.d.ts +7 -19
  255. package/dist/ui.js +76 -90
  256. package/dist/url-safety.js +3 -6
  257. package/dist/usage-guide.js +146 -0
  258. package/dist/validate.d.ts +7 -0
  259. package/dist/validate.js +309 -47
  260. package/dist/version.d.ts +1 -7
  261. package/dist/version.js +3 -7
  262. package/dist/worker.d.ts +11 -0
  263. package/dist/worker.js +435 -0
  264. package/documentation/README.md +63 -0
  265. package/documentation/architecture.md +696 -231
  266. package/documentation/auth.md +1069 -546
  267. package/documentation/code-mode.md +662 -356
  268. package/documentation/deploying.md +146 -0
  269. package/documentation/integrating.md +159 -0
  270. package/documentation/meta-tools.md +570 -216
  271. package/documentation/operating.md +135 -0
  272. package/documentation/operator-tests.md +97 -0
  273. package/documentation/operator-ui.md +132 -0
  274. package/documentation/provider-migration-0.29.md +65 -0
  275. package/examples/worker/AGENTS.md +34 -12
  276. package/examples/worker/README.md +293 -255
  277. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  278. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  279. package/examples/worker/src/connecta.config.ts +151 -0
  280. package/examples/worker/src/index.ts +14 -197
  281. package/examples/worker/wrangler.jsonc +15 -35
  282. package/package.json +126 -54
  283. package/templates/node/.dockerignore +1 -2
  284. package/templates/node/.env.example +20 -21
  285. package/templates/node/AGENTS.md +21 -17
  286. package/templates/node/Dockerfile +3 -5
  287. package/templates/node/README.md +140 -58
  288. package/templates/node/docker-compose.yml +11 -12
  289. package/templates/node/package.json +4 -2
  290. package/templates/node/src/connecta.config.ts +119 -0
  291. package/templates/node/src/index.ts +9 -154
  292. package/templates/node/src/provision-token.ts +17 -0
  293. package/AGENTS.md +0 -170
  294. package/dist/artifacts/connector.d.ts +0 -58
  295. package/dist/artifacts/connector.js +0 -730
  296. package/dist/artifacts/document.js +0 -86
  297. package/dist/artifacts/guide.js +0 -140
  298. package/dist/artifacts/html-scan.js +0 -329
  299. package/dist/artifacts/json.js +0 -78
  300. package/dist/artifacts/kv-store.d.ts +0 -20
  301. package/dist/artifacts/kv-store.js +0 -140
  302. package/dist/artifacts/markdown.js +0 -478
  303. package/dist/artifacts/module.d.ts +0 -37
  304. package/dist/artifacts/module.js +0 -66
  305. package/dist/artifacts/operations.d.ts +0 -197
  306. package/dist/artifacts/operations.js +0 -1002
  307. package/dist/artifacts/refresh.d.ts +0 -55
  308. package/dist/artifacts/refresh.js +0 -236
  309. package/dist/artifacts/routes.js +0 -206
  310. package/dist/artifacts/types.d.ts +0 -214
  311. package/dist/artifacts/types.js +0 -22
  312. package/dist/artifacts/validate.d.ts +0 -62
  313. package/dist/artifacts/validate.js +0 -651
  314. package/dist/artifacts.d.ts +0 -6
  315. package/dist/artifacts.js +0 -9
  316. package/dist/auth/bearer.d.ts +0 -11
  317. package/dist/auth/bearer.js +0 -50
  318. package/dist/providers/basecamp.js +0 -445
  319. package/dist/providers/linear.js +0 -217
  320. package/dist/providers/mixpanel.js +0 -247
  321. package/dist/providers/revenuecat.js +0 -317
  322. package/dist/providers/stripe.js +0 -253
  323. package/dist/providers/vercel.js +0 -1253
  324. package/dist/storage/file.d.ts +0 -15
  325. package/dist/storage/file.js +0 -455
  326. package/ethos.md +0 -93
  327. package/examples/worker/src/cloudflare-kv.ts +0 -43
  328. package/examples/worker/src/d1-activity-row.ts +0 -155
  329. package/examples/worker/src/d1-activity.ts +0 -147
  330. package/examples/worker/src/d1-storage.ts +0 -138
  331. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  332. package/templates/node/src/file-activity.ts +0 -182
package/CHANGELOG.md CHANGED
@@ -2,6 +2,856 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## 0.30.0 — 2026-10-09
6
+
7
+ Connecta 0.30.0 makes large results, sign-in and rich output dependable for agents, on both direct calls and programs. The six-tool surface from 0.29.0 stays. We measured the alternative, folding everything into `execute_code`, against it on Sonnet 5.5 and GPT-6-Luna (#765). The code-only surface scored lower and made a duplicate billable write, so it was not adopted.
8
+
9
+ Default `call_tool` and `call_destructive_tool` results now put their data in `content` and no longer send `structuredContent` or advertise `outputSchema`. Clients that prefer structured content, Claude Code among them, previously saw only a `{"format":"json"}` stub. Read the format from `_meta["dev.connecta/format"]`. For a truncated result, parse the first line of the text as a JSON notice. `resultMode: "value"` keeps the full structured envelope.
10
+
11
+ Programs no longer lose large data. A `connecta.call` result over the executor's inline cap is rebuilt from stash pages up to 1 MiB, and larger results come back as a handle for `connecta.result`. A program that returns more than 24,000 characters gets a pageable `resultId` in place of a truncated preview. If that run made a write, the notice says the write already ran. Paging checks the caller's connector grants on every page. Programs can forward images and other rich content with `connecta.emit(result)` or `result.content`, within the existing byte and block limits.
12
+
13
+ Downstream-OAuth handoff links use compact, opaque tokens that agents copy intact; links issued before the upgrade keep working until they expire. Auth failures on direct calls, program calls and catalog search now carry the sign-in link or operator instructions themselves, so agents no longer need a separate `authorize_connector` step. URL elicitation is unchanged.
14
+
15
+ Values submitted in JSON-Schema `writeOnly` argument fields are now treated like credentials. They are redacted from downstream result text, error text, program output and stored pages, with bounded work and memory. Values under 8 characters are replaced only where a structured field equals them, and prose containing them is withheld. Credential redaction of very large or deeply nested results no longer risks exhausting memory.
16
+
17
+ Custom `KVStorage` adapters must honor the new `expiresAtMs` option on `set` and `compareAndSet` and declare `capabilities: { absoluteExpiry: true }`. Construction rejects adapters without that declaration. The built-in memory, SQLite and D1 drivers already comply. Stash capacity charges now use storage time and hold until orphaned chunks expire, so a skewed or failing host can no longer over-admit stashes. The new maintained Infisical provider lets deployments manage projects, folders and secrets with machine-identity Universal Auth and no hand-written connector.
18
+
19
+ ### Added
20
+
21
+ - Add a maintained Infisical REST provider for machine-identity Universal Auth, projects, folders, and secret reads and writes. Tokens stay in memory and refresh before expiry, lists omit values by default, writes omit values and expose pending approvals, and credential-free OpenAPI checks cover every endpoint. Value-free results, including imports and approvals, return only validated identifiers and omit comments, descriptions, free-form names, reminder notes, and arbitrary nested objects. Project slugs are withheld because Infisical can derive them from free-text project names. Write recovery echoes omit secret values, secret comments, and folder descriptions without changing dispatched arguments. Comments are available only from explicit value reads. `metadataOmitted` reports withheld or unavailable metadata. This protects against careless human data entry on an honest server; adversarial encodings by a malicious server are out of scope. Known, accepted residual risk: environment slugs are returned as required routing identifiers. Infisical's UI derives them from environment names by default, so secrets entered in those names can appear in returned slugs. Operators must not put secrets in environment names or slugs. Secrets placed in identifiers or key names are out of scope. Submitted-value matching and request-secret registration are removed, preserving later program arguments and repeated writes with the same value. Rejected tokens never automatically resend writes; reconcile before an explicit retry. Existing `clientId`/`clientSecret` credentials work when the connector id is preserved. Core uncertainty and retry envelopes omit nested and composed `writeOnly` fields, withhold argument echoes when sensitivity cannot be resolved, and mark partial or withheld echoes with `argsRedacted: true`. Retry using the original arguments. Guarded base-URL validation refuses invalid configuration without quoting its value.
22
+ - Add an eval-only code arm, explicit route/outcome grading, offline baseline regrading and a paired A/B report with a pre-registered correctness and write-safety decision rule. Both agent runners assert the selected tool list; Claude also supports Haiku 5.5.
23
+ - Forward MCP rich content from programs with `connecta.emit(result)` or `result.content`, preserving native blocks and metadata within the existing paging and emission limits. Snapshot retained native blocks within byte, node, and depth limits before paging can yield so later connector mutations cannot change their content or bypass emission limits. Page oversized structured data before inspecting native presentation, and refuse cyclic or over-budget presentation without retaining it.
24
+
25
+ ### Changed
26
+
27
+ - Auth failures on direct calls, program calls, and catalog search/describe now include the authorization URL or operator credential instructions that `authorize_connector` returns. Each call or program run reuses one handoff per connector, without forcing reauthorization. Programs can read typed recovery through `error.data` as well as `error.details`; URL elicitation is unchanged.
28
+ - Separate the human product overview from agent task documentation. Route endpoint operation, deployment, integration, and repository maintenance through an agent index, preserve technical contracts and existing section links, and move the operator UI contract into its own guide.
29
+
30
+ ### Fixed
31
+
32
+ - Read Vercel's complete public MCP inventory from its linked category references, with bounded credential-free fetching and explicit unavailable findings for incomplete documentation.
33
+
34
+ Reconcile category URLs discovered across the entire index with published counts and category tool headings before comparing drift. Unrecognized formatting reports unavailable instead of silently omitting tools, and unused unrelated reference definitions do not block the inventory.
35
+
36
+ Resolve relative category destinations before requiring a count on each category link, exclude CommonMark code examples, and require the tool's known category to reconcile before reporting a removal. Disappearing categories require review, while unrelated count prose does not block the check.
37
+ - **Breaking:** Book each result stash reservation once with a finite deadline of booking time
38
+ plus a 30-second write budget plus the chunk TTL, normally 15 minutes. Stop the
39
+ write loop on timeout and return no paging ID. Absolute chunk and ledger expiry
40
+ prevents late or ambiguous writes from extending that bound. Custom KVStorage
41
+ adapters must honor the new `expiresAtMs` option on set and compareAndSet and
42
+ declare `capabilities: { absoluteExpiry: true }`; construction rejects legacy
43
+ adapters without that explicit opt-in. Rejected write responses retain the
44
+ original reservation because their dispatched operation may commit later.
45
+ Settlement and cleanup shorten or remove charges when possible; after failure
46
+ or exhausted retries, capacity may be over-held until the booked deadline.
47
+ Bound each cleanup/release or settlement path to 15 seconds, including storage
48
+ I/O, and abandon further work when its budget expires so stalled operations
49
+ cannot hang the caller after the write timeout.
50
+ Remove durable completion receipts and their recovery path.
51
+ State-file import and `migrate-state` report database failures with fixed
52
+ step descriptions, preventing SQLite trigger errors from printing imported
53
+ values (#726).
54
+ - Serve identical eval code-arm usage guidance through text, structured content, resource aliases and guest skills. Require clean matching runtime and task/harness provenance before evaluating the registered A/B decision; explicit mismatch diagnostics mark decisions NON-COMPARABLE.
55
+ - Require strict per-trial model, CLI, task and deadline provenance before the eval A/B decision. Count timeout, interruption and runner errors as failures regardless of cached status, report failure kinds per arm, and derive native code-arm skill manifests from the exact served guide bytes.
56
+ - Clarify the cross-connector join eval to select the highest-MRR customer with an open bug, and accept value-mode retained-result notices in the result-paging grader while preserving same-ID paging and single-fetch checks.
57
+ - **Breaking:** Keep default direct-call data and truncation previews visible in clients that prefer structured content. Successful `call_tool` and `call_destructive_tool` results with `resultMode` omitted or set to `"mcp"` now omit `structuredContent`, including its former `format` and paging fields. These tools also stop advertising `outputSchema`.
58
+
59
+ Migrate clients to read result data from `content` and format from `_meta["dev.connecta/format"]` instead of `structuredContent.format`. For truncated default-mode results, parse the first line of the content text as a JSON notice to read `resultId`, `nextOffset`, and `nextAction`, then page with `connecta.result`. Clients that need a structured envelope can request `resultMode: "value"` and keep reading `structuredContent.format` and the paging notice in `structuredContent.data`. Remove assumptions that either direct-call tool advertises an `outputSchema`; use content handling or the value-mode envelope instead. Value-mode and error envelopes retain their complete JSON mirrors.
60
+ - Require storage-side proof of expiry before pruning stash capacity charges, so an ahead caller clock cannot admit another stash while charged chunks remain readable.
61
+ - Make stash charge deadline tests deterministic under runner load across memory, SQLite, and D1.
62
+ - Page oversized call results inside programs through the existing result stash. Reconstruct bounded values across executors and return pageable handles for larger results, keeping completed writes successful and avoiding duplicate downstream calls. Preserve U+FEFF at result and page starts in program reconstruction and direct paging. Report unpageable oversized raw text containing unpaired UTF-16 surrogates without silently replacing its code units.
63
+ - Issue compact downstream-OAuth handoff URLs with opaque, unpadded base64url tokens so agents can copy them intact. Preserve exact signature verification, all identity and lifetime checks, and pre-upgrade links until their original expiry. Authorization guidance now tells agents to copy the URL unchanged and request a fresh link when it is invalid or expired.
64
+ - Stash oversized execute_code returns behind a pageable resultId, preserving the full redacted value without repeating completed writes. Share direct-call expiry, authority checks, page caps, and capacity limits; keep bounded truncation when paging is unavailable. Skill and catalog reads retain their grant dependencies through subsequent return stashes, including caught resolution failures and recovery metadata. Local guides follow connector visibility under exact-tool grants; downstream skills and resources retain whole-connector enforcement. Write warnings reflect only writes completed during the current run.
65
+ - Count Claude eval runner cleanup after successful conversation completion as a normal exit in paired comparisons.
66
+ Accept numeric 143 or signal SIGTERM only after successful completion and a harness SIGTERM sent before any observed child exit.
67
+
68
+ ### Security
69
+
70
+ - Redact submitted `writeOnly` argument values, including property names inside private objects and their encoded forms, from downstream results, errors, resource reads, connector usage guides, downstream skill files, program returns, logs, and emits before they reach agents, guest programs, or result paging storage. JSON results stay valid, with matching numbers and booleans replaced by a placeholder string, and nested JSON strings are decoded and checked. Short private values redact only equal structured fields and withhold prose that contains them, so identifiers and approval state remain; empty strings are exempt. Matching costs amortized constant work per scanned character regardless of how many values are registered, plus one budgeted confirmation per length where a long value's prefix appears; the matcher's memory, including at most 16 Ki runs of waiting confirmations per scanned text, is bounded across every credential and private value, and text that needs more work or runs is withheld. Redaction walks results iteratively and copies only what changes, so an oversized or deeply nested downstream result can no longer exhaust the heap or stack before paging, and one bounded work budget per redaction withholds only the affected field.
71
+
72
+ ## 0.29.0 — 2026-10-08
73
+
74
+ Connecta 0.29.0 adopts the MCP 2026-07-28 request envelope, binding client capabilities and client identity to each request. `server/discover` advertises the extensions served and private cache hints. Connecta-owned access, pool and deadline refusals return JSON-RPC errors; admission codes move to `-33001` and `-33002`. Modern clients must preserve the protocol-version and declared argument headers. `tools.listChanged` is false, and `subscriptions/listen` remains unsupported.
75
+
76
+ The tool list is six: `execute_code`, `call_tool`, `call_destructive_tool`, `search_tools`, `authorize_connector` and `skills`. `get_result` is removed; programs page retained direct-call results with `connecta.result(id, options)`. `resume_execution` remains removed, with no paused programs to resume. Guest calls return `{ data, format: "json" | "text" }`, programs report host-call counts, and `connecta.emit` delivers media without requiring `await`. All six tools advertise output schemas.
77
+
78
+ One fail-closed classifier decides `read` or `write`, with exact deployment overrides ahead of provider review and downstream annotations. Per-tool approval exemptions are gone. Root and named pools independently default to `read-only`; `trusted` pools allow program writes and annotate `execute_code` as a write. Approval belongs to the MCP host. Trust widens no connector or identity grant. Dispatched writes are never automatically replayed, and unknown outcomes require reconciliation.
79
+
80
+ Errors distinguish host authentication, downstream OAuth and provider permissions. Routing, argument and recoverable authentication failures carry a repair `nextAction`; other failures retain typed retry or reconciliation guidance. Argument refusals include bounded schema repair details. Discovery returns one flat page with catalog failures before tools, ranks exact tool names and addresses first, and reports endpoint-scoped service absences without substituting lookalikes. Program discovery defaults to JSON Schema; compact schemas are labeled as text.
81
+
82
+ Humans authenticate through Clerk or Cloudflare Access. Access requires trusted direct Worker context; Node uses Clerk. Machines use stored `cta_` tokens, and the generic static bearer adapter is retired. Downstream OAuth starts through an identity-checked, single-use `/connect/<id>` page. Capable hosts receive MRTR URL elicitation; other hosts receive a connection handoff. Public HTTPS deployments default to self-hosted CIMD where the authorization server supports it, with DCR fallback and issuer-bound static clients available. Disconnect removes local authorization and attempts revocation once when the saved server advertises it. State is one grant record per connector and owner, plus one expiring flow record per consent.
83
+
84
+ Direct calls relay downstream `input_required` through connecta's sealed state, bound to the principal, pool, target and original arguments. Only declared form and URL input capabilities are forwarded. Programs receive direct-call repair guidance instead. A consumed continuation cannot be resent; continuation failures are not retryable. The Skills extension serves connecta guides, connector guides and opted-in downstream skills through one caller-scoped registry, shared with `skills` and `connecta.skill()`. Upward resource reads remain skill-only.
85
+
86
+ Downstream clients reuse SDK negotiation verdicts and complete SQL-backed catalog caches. Host-computed identity, credential and pool partitions bound sharing; downstream cache hints cannot widen it. Programs read advertised downstream resources with `connecta.read("resource://<connectorId>/<encodedUri>")`. URI template matching uses bounded forward scans and refuses ambiguous matches, authority changes and unsafe paths before dispatch.
87
+
88
+ Persistent storage is one SQL key-value store with D1 on Workers and SQLite on Node. Workers KV, the JSON file store and optional compare-and-set paths are removed. Result-stash bounds now apply across processes or isolates sharing the store. One configuration schema supplies types, validation and defaults; unknown keys and invalid values fail construction. `describeConfig()` supplies a secret-free snapshot for the operator UI and doctor.
89
+
90
+ Maintained providers use `defineProvider` and folders containing implementation, `SKILL.md`, drift evidence and tests; exports and check inventories are generated. Hosted MCP implementations are reviewed presets. Notion, Vercel and Cloudflare default to hosted MCP with explicit REST complements. The GitHub provider combines hosted tools and a REST complement through a GitHub App, supporting organization and repository scopes together with repository-narrowed installation tokens. It acts as the App; repository creation and forks are absent, and workflow-file writes are off by default. Provider drift checks run locally, on provider pull requests and weekly, reporting findings without filing issues. Google Workspace providers retain domain-wide delegation, including draft-only Gmail and Slides comments.
91
+
92
+ The operator UI is rebuilt on React, Radix and Tailwind with hashed immutable assets on both runtimes. It shows caller-scoped configuration, catalogs, access and activity; capability changes remain in deployment code. Activity records the real package version independently of display `serverInfo`, validated client name and version, call classification and result size, without payloads. Shipped executors carry lifecycle brands; custom sandboxes explicitly accept cleanup responsibility through `customExecutor(executor, { lifecycle: "self-managed" })`.
93
+
94
+ The built-in artifacts feature, package subpath, pages, scheduled refresh and agent tools are removed. A leftover `artifacts:` or `artifactOrigin` option throws `ConfigError`. Existing artifact data is ignored and untouched. Deployments without the built-in feature need no artifact migration; dedicated downstream publishing services remain ordinary connectors.
95
+
96
+ Agent evals now use the 0.29 tools and guest API with correctness grading for destination, evidence and final answers. Sonnet 5.5 and GPT-6-Luna baselines and their measured failures are recorded in `eval/baselines/notes-0.29.md`. Runner limitations are explicit N/A outcomes. These diagnostic trials do not establish a model ranking or native CLI URL-elicitation support.
97
+
98
+ Security fixes harden Clerk audience binding and downstream OAuth completion binding. Credential redaction and payload-free operator records cover shared request boundaries. GitHub grants bind to repository IDs, catalog sharing respects host auth partitions, and resource URI validation is tightened. OAuth token requests refuse redirects, and refresh coordination preserves one-time dispatch across cancellation and grant resets.
99
+
100
+ Contributors use oxfmt through `npm run format`; `npm run format:check` joins the verification gates. Generated files, fixtures, baseline data and released changelog history retain their existing serialization. Deployments need no formatting migration.
101
+
102
+ Before upgrading an existing deployment, back up its state and database, preserve the credential-encryption key and identity namespaces, and prepare the replacement configuration and imports. Delete `discovery.persistCatalog` and `discovery.staleCatalogSeconds`. Replace the old registry freshness policy with `discovery.catalogMinTtlSeconds` and `discovery.catalogMaxTtlSeconds`, defaulting to 0 and 86400; `catalogTtlSeconds` remains the 300-second fallback when a downstream omits TTL hints. Explicit zero hints disable reuse. Delete `execute.approval`, `Connector.approval`, `execute.resumableWrites` and `execute.pausedRunTtlSeconds`. Set root `trust` and each `pools.<name>.trust` deliberately; named pools do not inherit root trust. Remove artifacts configuration, the `@zackbart/connecta/artifacts` import, artifact bindings and refresh jobs. Replace legacy `credentials` with `vault`, move root branding into `operatorUi({ branding })`, and replace boolean or object `accessTokens` configuration with `accessTokens(storage)`. Fix rejected options before starting the new version.
103
+
104
+ Move persistent storage before reopening traffic. On Node 22.13 or later, stop the old server and run `connecta migrate-state <state.json> <connecta.sqlite>` once, then use `sqliteStorage` and `sqliteActivityStore` from `@zackbart/connecta/sqlite`. Replace `CONNECTA_STATE_FILE` and `CONNECTA_ACTIVITY_FILE` with `CONNECTA_DATABASE`; the old JSONL activity log is not imported. Workers use `d1Storage` and `d1ActivityStore` from `@zackbart/connecta/d1`, replacing copied KV/D1 adapters. Existing `connecta_kv` and `tool_call_activity` tables remain readable; the example uses `CONNECTA_DB` for both. `fileStorage` is no longer exported from `/node`, and custom stores must implement `list` and `compareAndSet`. Deployments using `memoryStorage()` can ignore the persistent-store conversion.
105
+
106
+ For Workers KV, use the Worker example's `scripts/copy-kv-to-d1.mjs` or `copyKvToD1` from `/d1` under maintenance. Back up values with expiration metadata and D1, stop background writers, and drain requests, callbacks and refreshes. Wait at least 60 seconds or the longest KV cache TTL after the last write, and require stable listing/hash passes. Copy before switching to D1, resolve invalid entries and conflicts, and verify exact value hashes and absolute expiries. Preserve the existing vault key. Keep maintenance through startup and credential checks, then mark the source live with `--mark-live` or `markKvToD1Live` before reopening traffic. Later copies refuse stale KV state. Keep backups until the upgrade is verified. OAuth layout migration runs automatically on each owner's first grant read; issuer-stamped grants carry over, pending links and pre-issuer-binding grants need Connect again. Rolling back after that migration requires consent again.
107
+
108
+ Replace `@zackbart/connecta/auth/bearer`, `bearerToken` and `assertedPrincipal` with `accessTokens(storage)`. Provision a separate stored token per machine or represented human, update grants to its metadata ID and `access_token` actor, then rotate clients to `Authorization: Bearer cta_…` and remove the static secret and `X-Connecta-Principal`. Keep exact principal namespaces and IDs when preserving personal state; existing unrevoked `cta_` tokens survive the storage copy. Worker machines also need Access service credentials at the edge. Protect `/connect/*` and `/oauth/callback/*` with the intended Worker Access application. Clerk deployments enable `aud_claim_enabled`, reconnect using the advertised endpoint URL as `resource`, and verify refresh. Custom auth adapters replace `final` and `finalRefusals` with synchronous credential recognition; consolidate Access wrappers that depended on refusal fallthrough.
109
+
110
+ Replace upstream `DynamicWorkerExecutor` construction with `workerExecutor({ loader: env.LOADER })` from `@zackbart/connecta/worker`, keeping the optional `@cloudflare/codemode` peer. Wrap custom executors with `customExecutor(..., { lifecycle: "self-managed" })` and implement termination and cleanup. Existing `quickJsExecutor()` configurations need no change. Public provider import names survive the folder move. Existing Notion integration workflows select `surface: "api"` and rename REST tools to `integration_*`; Vercel and Cloudflare users migrate removed operations to live hosted schemas and configure retained REST complements under separate connector IDs. Follow `documentation/provider-migration-0.29.md`. Update client workflows to the flat discovery page and `{ data, format }` guest results, page direct results through `connecta.result`, and remove calls to `get_result` or `resume_execution`.
111
+
112
+ Start the upgraded deployment under maintenance and run `connecta doctor` with its required machine and edge credentials. Doctor checks health, SDK protocol negotiation, the exact current six-tool set and an actual executor program, and reports the negotiated revision and package version. With the UI enabled, `doctor --config` prints the caller-scoped secret-free snapshot instead of running diagnostics. Check existing OAuth and vault credentials, intended identity and pool grants, token revocation and activity, reissue pending connection links, then reopen traffic. UI, provider and contributor changes require no additional migration for deployments that do not use them. These notes apply whenever a deployment chooses to upgrade; the release includes no owner deployment cutover.
113
+
114
+ ### Added
115
+
116
+ - Add seven Google Slides comment tools: `list_comments`, `create_comment`, `create_comment_reply`, `update_comment_thread`, `update_comment_post`, `delete_comment`, and `delete_comment_reply` (#696). Reads include authors, replies and anchors, with revision/content-bound cursors and raw thread paging. Comment mutations are writes; trusted pools may run them in programs, and the host controls approval. The raw batch hatch accepts comment requests and preserves reply ids ahead of text under its result budget. Oversized direct write results can be paged with program `connecta.result` on trusted endpoints. Read-only endpoints return an inline truncation notice and preview without a write stash. Results distinguish batch acceptance from comment save confirmation; a comment write without `ALL_SAVED` reports `commentUpdateState` and instructs callers to re-read instead of repeating it.
117
+ - **`Connector.classification`: connectors report facts, the registry
118
+ classifies** ([#705](https://github.com/zackbart/connecta/issues/705)). A
119
+ connector may carry a reviewed `ToolClassification` as the readonly
120
+ `classification` field. `remoteMcp({ classify })` and maintained providers set
121
+ it to a deep-frozen copy, and their `listTools` now returns the downstream's
122
+ listing unclassified. The registry validates the field when it first reads a
123
+ connector, rejects it beside `staticTools`, and keeps request-local classified entries built from intake-redacted listing facts.
124
+ The SQL-backed SDK cache stores complete intake-redacted facts without registry
125
+ classification verdicts, and each request classifies them into fresh objects. It also observes catalog drift for such connectors itself, and
126
+ ignores their `catalogDrift()`. A decorator that filters, copies, or annotates a
127
+ listing, or mutates tools it listed or was handed, cannot turn a reviewed write
128
+ into a read. Wrappers keep the review only by forwarding the field:
129
+ `{ ...connector }`, `Object.assign`, and `Object.create` do; a forwarding class
130
+ that omits it serves an unreviewed connector, whose downstream annotations fail
131
+ closed when absent. Exact deployment-level overrides
132
+ ([#706](https://github.com/zackbart/connecta/issues/706)) apply ahead of review
133
+ and downstream annotations, regardless of wrapping.
134
+
135
+ Request-scoped catalogs own their definitions and serve deep copies to
136
+ discovery and invocation. Each connector dispatch also receives a fresh deep
137
+ copy of its definition. Mutating a dispatched definition or a discovery
138
+ result cannot change later discovery, schema validation, or write accounting
139
+ within the same program.
140
+ - Add a GitHub provider with mixed org and repository scopes through a GitHub App, owner-routed and repository-narrowed installation tokens, exact hosted-tool allowlisting, scope discovery, confined search, release writes, and workflow-file writes disabled unless explicitly configured. Add request-local Bearer authentication and an opt-in classification allowlist that hides unlisted tools.
141
+ - `copyKvToD1(kv, db, { source, cursor, overwriteFamilies, maxKeys, verify })`
142
+ on `@zackbart/connecta/d1` copies a 0.28 Workers KV deployment's state into D1
143
+ during a maintenance window, preserving OAuth grants, vault credentials,
144
+ `cta_` tokens, and absolute expiries. It skips oversized UTF-8 strings/rows,
145
+ bounds buffered bytes, and reports invalid/conflict/verification counts by
146
+ family only. Resume tokens require the same source id and an atomic claim;
147
+ error labels use class identity. Overwrite requires an explicit family list.
148
+ `markKvToD1Live` seals the source in D1 before traffic reopens, refusing later
149
+ copies unless explicitly overridden. The Worker runbook backs up and drains
150
+ writers, waits for stable KV, copies and verifies before deployment, checks
151
+ existing credentials under maintenance, then reopens traffic (#709).
152
+ - **Google Docs connection.** `@zackbart/connecta/providers/docs` exports
153
+ `docs(id, options)` with the same Workspace options as `gmail()`, plus
154
+ `DOCS_SCOPES` (exactly `documents`) and `DOCS_API_BASE_URL`
155
+ ([#681](https://github.com/zackbart/connecta/issues/681)). Six hand-written
156
+ tools: `get_document` (every tab, or one, rendered as markdown-ish text —
157
+ headings, lists, links, tables as pipe rows, footnotes, and every inline chip
158
+ by what it shows, a dropdown's selected value included — capped by
159
+ `maxChars` with an explicit marker, with `title`, `revisionId`, on
160
+ `withIndexes` the UTF-16 start and end index of every paragraph, table, and
161
+ table of contents, paged by cursor in 64 KiB pages so a default read always
162
+ fits the 256 KiB `execute_code` bridge, and a per-tab `notRendered` naming headers, footers,
163
+ floating images, unmarked suggestions, or unknown elements the text leaves
164
+ out; `raw: true` returns Google's resource instead, up to 4 MB),
165
+ `create_document` (title and an optional body; a body that fails after the
166
+ document exists names its id instead of inviting a duplicate), `append_text`
167
+ and `insert_text` (additive), `replace_all_text` (string or RE2, by tab,
168
+ reporting the count), and `batch_update_document`, a raw
169
+ `documents.batchUpdate` passthrough annotated as destructive and classified as a write, takes 1–100
170
+ requests of known generally available kinds (contents unvalidated), returns
171
+ Google's replies within the shared result budget (projected to ids and
172
+ counts, then cut to a counted prefix, still saying the batch applied), and
173
+ requires `requiredRevisionId`. Every other edit takes it optionally, and a
174
+ write naming one is revision-guarded, so a stale revision fails `conflict`
175
+ on Google's own reason code. Failures are classified by how far the request
176
+ got, from the shared client's outcome facts: a 4xx refusal passes through
177
+ as mapped and applied nothing; an edit sent with no answer back, answered
178
+ with a 5xx (whatever reason it carries), or redirected says its outcome is
179
+ unknown, non-retryably, because no Docs write is safe to send twice; only a
180
+ 2xx whose reply broke off, overflowed, or would not parse says the edit was
181
+ applied; and a create that may have left a document behind never invites a
182
+ second one. Ids Google sends back are copied only if a tool could take them
183
+ as input again, and otherwise dropped whole and named in `dropped`; titles
184
+ are clamped; and every result passes one final size check, which a read
185
+ answers with its way out and a write with a small acknowledgment that it
186
+ applied. There is no search or
187
+ list tool: finding a document is Drive's job, and `documents` cannot list. A
188
+ 404 stays `connector_call_failed`, because Google answers it for an id that
189
+ is unknown and for one not shared with the caller alike. Setup, including
190
+ the Admin console scope, is documented on `docs()`; drift is checked against
191
+ the Docs Discovery document.
192
+
193
+ Trusted pools permit program writes; read-only pools use `call_destructive_tool`.
194
+ The MCP host controls approval.
195
+ - **Draft-only Gmail connection.** `@zackbart/connecta/providers/gmail`
196
+ exports `gmail(id, { purpose, serviceAccount, subject, title?,
197
+ instructions?, callAdmission?, maxResultBytes?, baseUrl? })`, plus
198
+ `GMAIL_SCOPES` and `GMAIL_API_BASE_URL`. Eight hand-written tools:
199
+ `search_threads` (Gmail search syntax, cursor-paged, each thread summarized),
200
+ `get_thread`, `get_message` (the message's own body — never an attached or
201
+ forwarded email's — decoded from its charset, HTML converted only when no
202
+ text part exists, fetched from the attachments endpoint when Gmail stored it
203
+ apart, reported as `bodyFormat: "unavailable"` rather than empty when too
204
+ large to read, capped by `maxBodyChars` with an explicit marker, and
205
+ metadata only for attachments, which include unnamed inline images and any
206
+ other part that is not body text), `list_labels`, `list_drafts`,
207
+ `get_draft`, `create_draft`
208
+ (To/Cc/Bcc, RFC 2047-encoded headers, an optional HTML alternative, and
209
+ `replyToMessageId`, which sets the thread, `In-Reply-To`, `References`, a
210
+ `Re:` subject, and every recipient of the replied message's Reply-To or
211
+ From, parsed as an RFC 5322 address list), and `update_draft` (replaces the
212
+ body; keeps From, To, Cc, Bcc, Reply-To, Subject, the thread, and the reply
213
+ headers unless restated, and no other header; refuses, unchanged, a draft
214
+ with attachments, inline images, or anything else Gmail's whole-message
215
+ update would delete, and one nested deeper than it inspects). Every result
216
+ is built to stay under 192 KiB of JSON, so it reaches a program through
217
+ `execute_code`'s 256 KiB bridge as well as `call_tool` — the whole result,
218
+ wrapper and cursor included, measured as sent rather than estimated:
219
+ `get_thread` and `list_labels` page by cursor when a thread or label set is
220
+ larger, every string field is bounded, an identifier far past any Gmail id
221
+ is dropped rather than cut and the drop is said — `omittedIds` names the
222
+ field, `labelIdsOmitted`, `labelsOmitted`, and `attachmentsOmitted` count
223
+ what a list left out — a saved draft whose id Gmail returned unusably still
224
+ reads `saved: true` with a note pointing to `list_drafts`, a body past
225
+ the limit ends with a marker at any `maxBodyChars`, and an untouched
226
+ `raw: true` message past it is refused with the way forward. Every cursor is
227
+ bound to the tool and arguments that issued it, and a thread or label page
228
+ resumes by the identity of what came before: a message or label added or
229
+ removed before that point answers `conflict` rather than repeating or
230
+ skipping one. There is no send, delete, or label tool and no raw
231
+ hatch; `gmail.compose` technically permits sending, and the tool surface is
232
+ what forbids it. `create_draft` is an additive write and `update_draft` a
233
+ destructive one. Both classify as writes; trusted pools permit them in programs,
234
+ and the host controls approval. The transport is confined beneath `users/me`, so the
235
+ token's subject is the only mailbox a request can reach. Setup — Cloud
236
+ project, API, service account with no IAM roles, JSON key (and the
237
+ `iam.disableServiceAccountKeyCreation` override some organizations need), and
238
+ the Admin console's domain-wide delegation entry with exactly
239
+ `gmail.readonly` and `gmail.compose` — is documented on `gmail()` itself.
240
+ - **Google Drive connection.** `@zackbart/connecta/providers/drive` exports
241
+ `drive(id, options)` with the same Workspace delegation options, plus
242
+ `DRIVE_SCOPES` (exactly `https://www.googleapis.com/auth/drive`) and
243
+ `DRIVE_API_BASE_URL`. Seventeen hand-written tools, every one reaching shared
244
+ drives as well as My Drive. Reads: `search_files` (Drive query syntax, trash
245
+ left out unless asked, `user`/`drive`/`allDrives`/`domain` corpora, Drive's
246
+ own `incompleteSearch` surfaced), `list_folder_items`, `get_file`,
247
+ `get_file_content` (Docs exported as Markdown, Sheets as CSV of the first
248
+ sheet only, Slides as text, text files read by range and cut at `maxChars`
249
+ code points, never inside a character, binaries as base64 when the encoded
250
+ file fits the result and never past 1 MiB, and anything else — a larger
251
+ binary, a drawing, a form, a folder, a shortcut — as `format: "unavailable"`
252
+ with a note rather than an empty success; the caps hold on the bytes the
253
+ download returns, never on the size metadata reported, so a file that grew,
254
+ shrank, or emptied between the two reads is still read right — a download
255
+ is read only one byte past its cap and the rest of the stream cancelled
256
+ unread, even from a server that ignores the range, and a range answered
257
+ `416` with `Content-Range: bytes */0` is a verified empty file — and Drive's
258
+ 10 MB export limit is named from Google's own reason code),
259
+ `list_permissions`, and `list_shared_drives`. Every read is built to stay
260
+ under `maxBytes` of JSON, envelope and cursor included: 192 KiB by default,
261
+ which a program inside `execute_code` always receives, from 64 KiB to 4 MiB
262
+ on request, above the default for a direct `call_tool` only. A final guard
263
+ around every tool refuses any result past that bound rather than returning
264
+ it; a write it stops has applied, and says to re-read rather than repeat. A listing that would outgrow it stops early with a
265
+ cursor that resumes at the first row left out, and fails `conflict` rather
266
+ than skip or repeat a row if that Drive page has changed since — the cursor
267
+ carries fingerprints of Drive's raw ids for that page, in order, and of the
268
+ last one returned. A cursor is never issued that could not be accepted back:
269
+ a Drive page token too long to carry fails, saying so.
270
+ Every cursor is bound to its tool and the arguments that decide its rows,
271
+ and refused elsewhere. Content is cut with a marker naming the bound that
272
+ cut it. Names are cut at 2 KiB in listings and content results and 32 KiB in
273
+ `get_file`, descriptions at 64 KiB, other strings Drive does not bound at
274
+ 1 KiB, and owners and parents at ten; identifiers are never cut, and one that
275
+ is malformed is left out. Every such field is named in the result's
276
+ `truncatedFields`.
277
+ Additive writes:
278
+ `create_folder` and `restore_file`. Destructive writes: `create_file` (text
279
+ or base64 content as one multipart upload, or an empty file; `convertTo`
280
+ imports it as a Google Doc, Sheet, or Slides file) and `copy_file`, because
281
+ a new file takes its folder's sharing and so discloses its content to
282
+ everyone a shared destination reaches. These classify as writes, as do `update_file_content`,
283
+ `update_file` (rename, description), `move_file` (a move changes inherited
284
+ sharing), `trash_file`, `share_file` (user, group, domain, or anyone with
285
+ the link, up to writer or organizer, emailing no one unless
286
+ `sendNotificationEmail`), `update_permission`, and `delete_permission`.
287
+ A create, copy, or share that may have landed — sent with no answer,
288
+ answered with a redirect it never follows, answered 5xx (a rate-limit reason
289
+ on a 5xx included), or accepted with a reply that broke — is never
290
+ retryable and names the read to check before repeating it, since a repeat
291
+ makes a second one. Only a write Google answered 2xx is ever said to have
292
+ applied. Writes that set fixed values (`update_file_content`, `update_file`,
293
+ `trash_file`, `restore_file`, `update_permission`) are sent as idempotent, so
294
+ a 5xx to them stays a retryable outage.
295
+ There is no permanent delete,
296
+ no empty-trash, no ownership transfer, and no raw hatch. A 404 says the file
297
+ may be missing or hidden from this account, because Drive does not say
298
+ which. Setup — the Google Drive API and the one scope on the delegation
299
+ entry — is documented on `drive()` itself.
300
+
301
+ Trusted pools permit program writes; read-only pools use `call_destructive_tool`.
302
+ The MCP host controls approval.
303
+ - **Google Sheets connection** ([#682](https://github.com/zackbart/connecta/issues/682)).
304
+ `@zackbart/connecta/providers/sheets` exports `sheets(id, options)` with the
305
+ same Workspace options as `gmail()`, plus `SHEETS_SCOPES` and
306
+ `SHEETS_API_BASE_URL`. Nine hand-written tools: `get_spreadsheet` (title,
307
+ sheets with ids and grid sizes, named ranges as A1 with the sheet title
308
+ always quoted, never cell values; `raw: true` for Google's untouched
309
+ metadata) and `get_values` (one to twenty A1 ranges through
310
+ `values:batchGet`, value and date render options, paged by whole rows under
311
+ `maxCells` and `maxBytes` — never more, an oversized row is refused — with
312
+ the default byte budget sized to what `execute_code` can receive and at most
313
+ 4 MiB for a direct `call_tool` read, which the result stash can still page,
314
+ and a cursor bound to the call's spreadsheet, ranges, and render options
315
+ that only ever continues the caller's own ranges; cells over 5,000 characters cut with a marker, and
316
+ `maxCellChars` up to 50,000 to read one whole) are read-only;
317
+ `create_spreadsheet`, `add_sheet`, and `append_values` (pinned to
318
+ `INSERT_ROWS`, so nothing below the table is overwritten) are additive
319
+ writes; `update_values`, `batch_update_values`, `clear_values`, and
320
+ `batch_update_spreadsheet` — Google's own `batchUpdate` requests, passed
321
+ through untouched and classified as a write, its replies cut to their kind,
322
+ ids, and counts when too large to deliver — are destructive. Writes take a
323
+ required `RAW` or `USER_ENTERED` and at most 50,000 cells per call. Value
324
+ updates and clears of fixed ranges are idempotent, so a 5xx stays
325
+ retryable; an append or create whose outcome is unknown says what to read
326
+ before repeating it — the table, or a Drive search for the title. A 404
327
+ never claims absence, since a spreadsheet is a Drive file that may simply not
328
+ be shared with the caller. Listing and finding spreadsheets is Drive's job.
329
+ The one scope, `https://www.googleapis.com/auth/spreadsheets`, and its setup
330
+ are documented on `sheets()`.
331
+
332
+ Trusted pools permit program writes; read-only pools use `call_destructive_tool`.
333
+ The MCP host controls approval.
334
+ - **Google Forms connection.** `@zackbart/connecta/providers/forms` exports
335
+ `forms(id, options)`, taking the same Workspace options as `gmail()`, plus
336
+ `FORMS_SCOPES` and `FORMS_API_BASE_URL`. Six hand-written tools: `get_form`
337
+ (title, description, quiz and publish state, `revisionId`, and every item
338
+ with the question ids answers are keyed by — one per grid row — their types,
339
+ options, and required flags; long descriptions and option lists cut with
340
+ explicit markers; `raw: true` for Google's whole Form), `list_responses`
341
+ (cursor-paged, an optional exclusive `submittedAfter` instant, each answer
342
+ labeled with its question's title in form order, with any quiz grade and
343
+ grader feedback, answers and feedback over 2,000 characters cut) and
344
+ `get_response` (one whole, with file ids, quiz grades, and feedback text and
345
+ links; `questionIds` narrows it), `create_form` (title, Drive file name,
346
+ optionally unpublished — all Google accepts at creation), `update_form_info`
347
+ (replaces the title or description), and `batch_update_form` (Google's own
348
+ batchUpdate requests, each exactly one of its six kinds, all or none, under a
349
+ required `requiredRevisionId` so an edit never lands on a form someone
350
+ changed since it was read; a stale revision fails `conflict`). Every result
351
+ stays inside 192 KiB, under `execute_code`'s 256 KiB host-result bridge,
352
+ measured whole, cursor included: a response page ends early and continues
353
+ after the last response it returned, only while Google's page still holds
354
+ the same responses in the same order (otherwise `conflict`), every
355
+ projected text is bounded in bytes as well as characters and cut without
356
+ splitting a surrogate pair, a large form pages its items under a cursor
357
+ bound to its revision — continuing inside a grid too large for one page, so
358
+ every question id is reachable — a response too large to read whole names the answers
359
+ it left out in `omittedQuestionIds`, `raw: true` refuses a form too large
360
+ for one result, and a batch reply too large to return whole drops question
361
+ ids, then everything but counts, marked `truncated` and still reported as
362
+ applied. `create_form` is additive; the other two writes are
363
+ destructive. A write Google may have applied — no answer, a 5xx or redirect after it
364
+ arrived, or a reply that broke — is reported as not retryable (no Forms
365
+ write is sent as idempotent), and a create says to look in Drive before
366
+ creating again. There is no list, delete, share, publish, or watch tool: Drive
367
+ lists forms, and this connection requests no Drive scope. A form is a Drive
368
+ file, so a 404 is reported as unknown-or-not-visible, never as absence.
369
+ Setup, with exactly `forms.body` and `forms.responses.readonly` for the
370
+ domain-wide delegation entry, is documented on `forms()` itself.
371
+ - **Google Workspace domain-wide delegation, shared by every Workspace
372
+ provider.** `serviceAccount` is `{ clientEmail, privateKey, clientId? }` or
373
+ the downloaded JSON key's text, from deployment secrets rather than the vault,
374
+ because one key serves every Workspace connector; anything but a complete,
375
+ exactly-encoded PKCS#8 RSA key of at least 2048 bits throws at construction,
376
+ and no key ever appears in
377
+ a message. `subject` is a function from the authenticated
378
+ `AuthenticatedIdentity` to a Workspace address — sync or async, for a
379
+ directory lookup, with the call's abort signal — or one fixed address for
380
+ shared or scheduled use. The function is never called for an open
381
+ deployment's anonymous requests or a call no request admitted; those, and an
382
+ `undefined` answer, fail `auth_required` before any request leaves.
383
+ Arguments, headers, and programs cannot choose the subject. The RS256
384
+ JWT-bearer assertion is signed with Web Crypto, so it runs unchanged on Node
385
+ and Workers with no new dependency. Access tokens are cached in memory only —
386
+ never in storage — per service account, key, subject, and scope set,
387
+ replaced a minute before expiry, and bounded at 512. Concurrent callers share
388
+ one mint through plain outcomes and a deadline, never another request's
389
+ signal, so a cancelled owner sends a waiting caller in another Worker request
390
+ to mint for itself. A 401 forgets only the token it rejected and replays the
391
+ request once, so a streamed request body is refused up front. A write that
392
+ names the revision it was made against passes `{ revisionGuarded: true }`,
393
+ and a stale revision — FAILED_PRECONDITION or ABORTED on HTTP 400 or 409 —
394
+ then arrives as `conflict` with fixed words to re-read and retry, unless
395
+ Google names a more specific reason — a disabled API, a quota, a missing
396
+ scope, or a precise permission refusal — which always decides first; otherwise
397
+ a refused precondition is reported neutrally in Google's own words. A
398
+ product can read Google's reason codes for any mapped failure — sanitized
399
+ tokens only, never its prose — and `exportSizeLimitExceeded`,
400
+ `domainPolicy`, and `insufficientFilePermissions`/`forbidden` refusals
401
+ name themselves precisely. A download or export can pass `{ maxBytes }` to
402
+ read only a prefix: the body is streamed, the rest is cancelled unread, and
403
+ the result says whether it was `truncated`, with its HTTP status and
404
+ `Content-Range`, so an empty file's 416 arrives as an empty result. A write
405
+ is never told to retry when it may have landed: one Google accepted whose
406
+ reply broke off or overflowed, one answered with a redirect, one sent with
407
+ no answer at all, and one Google answered with any 5xx — whatever reason it
408
+ named — fail as non-retryable with words saying to re-read its target first,
409
+ the verdict core's `write_outcome_unknown` gives a trusted-pool program write.
410
+ Only a 429, or a 4xx naming a quota, is a rate limit for a write. A read
411
+ stays retryable, as does a write the provider marks `{ idempotent: true }`.
412
+ An error status is a refusal even when its body cannot be read. A product
413
+ can ask how far any failed request got: before sending, awaiting a
414
+ response, reading an accepted reply, redirected, a server error on a write,
415
+ or refused. Google's
416
+ refusals map to what fixes them: `unauthorized_client`
417
+ names the client ID and the exact scopes to authorize, `invalid_grant` names an
418
+ unknown or suspended user, a deleted key, or clock skew, and a disabled API or
419
+ a missing scope says so.
420
+ - **Google Slides connection.** `@zackbart/connecta/providers/slides` exports
421
+ `slides(id, options)` with the same Workspace delegation options as `gmail()`,
422
+ plus `SLIDES_SCOPES` and `SLIDES_API_BASE_URL`. It requests exactly
423
+ `https://www.googleapis.com/auth/presentations`. Fifteen hand-written
424
+ tools, seven of them for comments (below). Of the other eight, four are
425
+ reads: `get_presentation` (title, page size, `revisionId`, a
426
+ layout preview, and each slide's text in reading order — top to bottom,
427
+ then left to right, rotated and nested groups composed — with table cells,
428
+ alt text, linked charts' spreadsheet ids, speaker notes, and empty
429
+ placeholders' ids for filling a new slide, under a field mask that leaves
430
+ styles behind, capped per slide), `get_page` (any slide, layout, master,
431
+ notes page, or the notes master by objectId: every element in reading order with its group and
432
+ placeholder type, index, and parent — what `placeholderIdMappings` needs —
433
+ and text continued across pages), `list_layouts` (masters, each followed by
434
+ its layouts), and `get_slide_thumbnail` (the short-lived link and size,
435
+ never the image). `create_presentation` and `create_slide` are additive
436
+ writes; `replace_all_text` (literal, case-sensitive by default, several
437
+ replacements in one atomic batch, optionally at a required revision) and
438
+ `batch_update_presentation` (1 to 100 raw Slides requests, each refused
439
+ locally unless it is one known Request kind, always at a required
440
+ `revisionId`, its replies bounded — every id at every depth kept whole,
441
+ large fields named in `cut`, overflow counted, and the write reported as
442
+ applied) are annotated as destructive and classified as writes. Every write result is size-checked too: copied
443
+ ids and revisions are whole or flagged, never cut, and a result that still
444
+ cannot be delivered after Google's 2xx is refused with "applied — do not
445
+ repeat it; re-read". A write refused because the deck changed since its
446
+ revision is a `conflict`, and a create whose outcome is unknown says what to
447
+ look for before creating again.
448
+ Every read result is built under the shared Workspace result budget, so it
449
+ is deliverable inside a program and directly alike, and nothing it cannot
450
+ carry is lost: cut text carries the `get_page` cursor that continues it
451
+ character by character, a crowded slide names the cursor for the elements
452
+ not shown, the layout preview continues in `list_layouts`, and `raw: true`
453
+ pages Slides' own JSON, sending an element too large for one result in
454
+ chunks that concatenate and parse. Every cursor is bound to the deck, page,
455
+ mode, and the revision it was read at — or, for a viewer Slides gives no
456
+ revision, a SHA-256 of the exact content its paging depends on — so a deck that
457
+ changed between pages is a `conflict` to restart, never a skipped or
458
+ repeated slide. Slides cannot list decks, and the guide says that is
459
+ Drive's job. A 404 is reported as unknown-or-not-visible, because a deck is
460
+ a Drive file. Setup is documented on `slides()`.
461
+
462
+ Trusted pools permit program writes; read-only pools use `call_destructive_tool`.
463
+ The MCP host controls approval.
464
+ - **Google Discovery drift checks.** `npm run providers:check -- --provider
465
+ gmail` reads Gmail's credential-free Discovery document and digests the nine
466
+ methods the tools call like any other touched endpoint, and also reports a
467
+ touched method that stops accepting the provider's delegated scopes. A
468
+ manifest with `"format": "google-discovery"` and a `scopes` list is all a
469
+ further Workspace product needs.
470
+ - `Connecta.describeConfig()` returns a secret-free snapshot of the running
471
+ configuration — limits with their `default`/`config` source, auth providers,
472
+ identity rules, pools, modules (with the activity store's kind and
473
+ retention), the storage adapter's kind, branding, and each
474
+ connector's source, endpoint, auth mode, credential slot labels, call
475
+ admission, and static tools — for the operator UI and `connecta doctor`.
476
+ `Connector.describe()` reports a connector's own facts and is implemented by
477
+ `remoteMcp()`, `api()`, and every provider. Every URL
478
+ either reports keeps origin and path only (a root-relative favicon, its path),
479
+ and a URL of any scheme but http(s) is omitted.
480
+ `defineConfig((env) => …)` declares a deployment's configuration as a
481
+ function of its environment (#705).
482
+ - Evaluate hosted Google Workspace and Planning Center capabilities while retaining delegated/PAT API providers, and report public provider contract drift on provider pull requests and weekly without blocking the check gate.
483
+ - `@zackbart/connecta/d1` exports `d1Storage(db)` and
484
+ `d1ActivityStore(db, { retentionDays })`; `@zackbart/connecta/sqlite`
485
+ (Node-only, on the built-in `node:sqlite`) exports `openSqlite(path)`,
486
+ `sqliteStorage(db | path)`, `sqliteActivityStore(db | path, { retentionDays })`,
487
+ and `importStateFile(db, statePath)`. Both drivers share one SQL store; each
488
+ creates its tables on first use and reads a 0.28 `connecta_kv` or
489
+ `tool_call_activity` table as is, adding activity columns it lacks. Activity
490
+ writes prune a bounded batch past the retention window, 90 days by default, so
491
+ no cron is needed. `connecta migrate-state <state.json> <connecta.sqlite>`
492
+ copies a 0.28 `fileStorage` state file into SQLite once, keeping any key the
493
+ database already holds. Every storage key is built in `src/storage/keys.ts`,
494
+ which lists each key family's scope, version, codec, and TTL (#705).
495
+ - Relay downstream form and URL input requests on direct MCP calls using encrypted, signed, single-use state bound to the principal, pool, target, and original arguments. Gate elicitation by the host's declared capabilities, bound rounds and payloads, and keep program input requests unsupported with direct-call guidance.
496
+
497
+ Refuse private-state echoes before publishing prompts, results, or catalogs, and prevent OAuth refresh or redirects from resending write continuations.
498
+ - Ask MCP 2026-07-28 hosts with URL elicitation support to connect a service when a call needs authentication. Authenticate retries with a deployment-held key and bind them to the principal, endpoint, connector, tool, arguments or code, round, and expiry. Decline and cancel stop without dispatch. Writes elicit only for Connecta-owned credential resolution failures before connector invocation. Programs also stop recovery after any write-classified handler was entered; each retry checks its own round. Raw fetches and custom transports cannot establish retry eligibility. These failures require reconciliation before retrying and retain manual connection guidance. Reads may re-run after dispatch only when classified from a catalog accepted within its TTL. The host records every entered call's classification and freshness; stale-fallback entries block program recovery. Accept rechecks every entered read before restarting any call and refuses changed classifications or digests with `auth_replay_refused` and reconciliation guidance. Normal stale-fallback calls retain their existing behavior. Other hosts receive the existing recovery envelope with a connection link.
499
+ - Programs can read downstream MCP resources with `connecta.read("resource://<connectorId>/<encodeURIComponent(downstreamUri)>")`. Reads require a whole-connector grant, share host-call and connector admission limits, and redact credential echoes before reaching the guest or agent.
500
+ - Serve usage, connector guides and opted-in downstream skills through one caller-scoped registry. Advertise the Skills extension and skill-only resources, with private cache hints, five-entry skill pages and preserved downstream digests. Both the skills meta-tool and connecta.skill read the same documents. Connector guide bodies gain Agent Skills frontmatter; connector:<id> remains a one-release lookup alias. Sent credential echoes pass through the existing agent redaction boundary, including binary supporting files.
501
+
502
+ Downstream Skills listing and file reads bypass the SDK response cache while sharing request-scoped auth with ordinary resource reads. Negotiation reuse includes the Skills opt-in.
503
+ - Record the build-time package version independently of display serverInfo on every activity event. Persist package and validated client facts in SQL history, migrate old tables with a nullable column, and expose them read-only at /ui/api/activity and in Activity row details. Older calls without request IDs remain separate rows. Doctor uses and reports the same generated package version.
504
+ - Add the typed operator UI contract and read-only `/ui/api/config` endpoint with a caller-scoped config snapshot, classified live tools, catalog age, payload-free last-call facts, and effective grants, pools, trust tiers, and permissions. `connecta doctor --config` prints the same secret-free snapshot as JSON.
505
+ - Add operator pages for deployment health, connectors, tool catalogs, access, request activity, and configuration on the typed, viewer-scoped contract. Preserve factory option presence so omitted token and transport options are marked as defaults.
506
+
507
+ ### Changed
508
+
509
+ - **Breaking:** **`defineProvider()` and `remoteMcp({ classify })`**
510
+ ([#705](https://github.com/zackbart/connecta/issues/705)). The root entry
511
+ exports `defineProvider()`, the one shape for a maintained provider: name,
512
+ title, kind, maintained skill, optional reviewed classification, and a
513
+ synchronous `create`. It validates the options every provider shares, renders
514
+ the usage guide with deployment instructions appended, and exposes its
515
+ `definition` on the factory. It imports neither transport, so `api()`
516
+ providers built with it gain no MCP or Effect graph. `remoteMcp()` accepts
517
+ `classify: { tools: { name: "read" | "write" | "destructive" | { verdict,
518
+ reason?, schemaDigest? } } }`, validated at construction. It fails closed,
519
+ and catalog drift is reported against it. Linear is the first provider
520
+ converted. Its tool names, verdicts, titles, descriptions, credential slot,
521
+ guides, and drift counts match 0.28 (`test/linear-snapshot.test.ts`). The one
522
+ change is stricter: a reviewed Linear create such as `create_issue_label`
523
+ now stays a write even when the downstream annotates it `readOnlyHint: true`.
524
+ `linear()` reports a blank purpose as `linear("<id>") requires a non-empty
525
+ purpose`, and rejects a blank `title`, a non-string `instructions`, and an
526
+ unknown `authScope` at construction. `LINEAR_VETTED_CATALOG` is deprecated in
527
+ favor of `linear.definition.classify`.
528
+ - **Reviewed hosted writes stay writes**
529
+ ([#705](https://github.com/zackbart/connecta/issues/705)). The Basecamp,
530
+ Cloudflare, Mixpanel, Notion, RevenueCat, Stripe, and Vercel providers now
531
+ apply the same rule as Linear: a tool their release reviewed as a write stays
532
+ a write when the downstream annotates it `readOnlyHint: true`. Before, such a
533
+ create was served as a read and ran unapproved. Their reads, destructive
534
+ tools, unlisted tools, and drift counts are unchanged.
535
+ - Contributors: CI runs Chromium only for relevant PR paths and always on `main` and publish, with a versioned browser cache and bounded installation retries; local `check` still runs the full suite (#705).
536
+ - Validate Clerk OAuth audiences on `/mcp` and pool endpoints for both JWT
537
+ and opaque tokens. Enable Clerk's `aud_claim_enabled` setting and request the
538
+ endpoint's canonical URL as `resource`. Omitted or empty `allowedOAuthClientIds`
539
+ requires bound tokens; an explicit list admits unbound tokens from dedicated
540
+ clients only. Clerk browser session tokens authenticate operator routes only.
541
+ Rejections log fixed reason codes, and the MCP `401` metadata challenge is
542
+ unchanged.
543
+ - Reject direct upstream `DynamicWorkerExecutor` construction, including
544
+ subclasses, before creating runtime resources. The error names the required
545
+ `/worker` import and configuration migration; custom executors remain
546
+ supported (#704).
547
+ - Require a non-enumerable, versioned lifecycle brand on executors before
548
+ creating runtime resources. `/worker` and `/quickjs` carry the brand across
549
+ package copies and bundles; unbranded executors fail with all migration
550
+ options. Custom sandboxes opt in with
551
+ `customExecutor(myExecutor, { lifecycle: "self-managed" })` (#704).
552
+ - Replace the contributor ethos with principles, decision history, and test-backed
553
+ invariant and MCP coverage records. Evidence comes from passing cases in the
554
+ full Node run, so fixtures, non-registering calls, and runtime skips cannot
555
+ certify coverage. Deferred MCP features remain declined for 0.29.0 (#705).
556
+ - Upgrade `@modelcontextprotocol/client` and `@modelcontextprotocol/server` to 2.3.1. The SDK binds stored credentials to their issuing authorization server and propagates token-save failures. Resource transport redirects remain confined to the same origin; credential-bearing OAuth token requests refuse every redirect. Connecta overrides the server SDK's 4 MiB request-body default on both protocol legs, preserving `listen()`'s `maxBodyBytes` and the Workers platform bound. The SDK owns the closed-transport legacy-handshake race, removing connecta's workaround for typescript-sdk#2864. Teardown regressions cover workerd rejection handling.
557
+ - **A 2026-07-28 request must carry `MCP-Protocol-Version`.** The 2026-07-28
558
+ transport requires the header on every POST and has the server reject a
559
+ request without it, so an intermediary routing on the header and the server
560
+ executing the body cannot disagree; server 2.0.0 served such a request
561
+ anyway. A client breaks here only if it puts the
562
+ `io.modelcontextprotocol/protocolVersion` claim in the request's `_meta`,
563
+ sends `Mcp-Method`, and omits `MCP-Protocol-Version` — a hand-rolled modern
564
+ client, or a proxy that strips the header. It must send the header with the
565
+ same value as the claim. Defaulting the header for it is not open to
566
+ connecta: the spec's allowance to assume `2025-03-26` covers only clients
567
+ older than 2025-06-18, which send no 2026-07-28 claim and are still served
568
+ on the legacy leg.
569
+ - **A JSON-RPC batch holds at most 100 messages.** The cap is the server
570
+ SDK's, not the protocol's: batching left the protocol in 2025-06-18, which
571
+ sets no size for the 2025-03-26 clients that may still send one. Server 2.1.0
572
+ answers a longer batch `400` with `-32600` and dispatches none of it; the cap
573
+ is a constant with no option, and connecta keeps it rather than reimplement
574
+ the SDK's body handling. A client that batches more must split the batch.
575
+ - **Breaking:** `api()`, `remoteMcp()`, every maintained provider, `operatorUi()`,
576
+ `accessTokens()`, `activityHistory()`, `d1ActivityStore()`, and
577
+ `sqliteActivityStore()` refuse an unknown
578
+ option at construction, naming its path — `api("crm").maxResultByte`,
579
+ `operatorUi().branding.theme.accentColor` — instead of accepting and ignoring
580
+ it. A discriminated option with a missing or unrecognized discriminant —
581
+ `remoteMcp()`'s `auth.type`, a provider's `auth.type` or `surface` — throws
582
+ with the valid values instead of constructing with no authentication.
583
+ Configuration and factory options are read once, by property descriptor, into
584
+ the plain copy core resolves: a getter or setter on a configuration object or
585
+ array refuses to construct and is never run, and no Proxy trap is invoked
586
+ beyond inspection. An array or class instance where an options object belongs
587
+ (`remoteMcp([...])`, `callAdmission: []`) throws with its path, and every
588
+ string map (static `headers`, `authorizationParams`, `tokenRequestHeaders`)
589
+ must hold string values. An `api()` tool is checked in place and passed
590
+ through as given, so a class-instance tool keeps its prototype handler and
591
+ private fields; a tool without a handler function refuses to construct. Pool names and `classification` map keys such as `__proto__` and
592
+ `constructor` are ordinary entries (#705).
593
+
594
+ Provider definitions declare a closed `options` shape with `optionsOf<T>()`.
595
+ `defineProvider()` and the remaining providers use one descriptor-validation
596
+ path and stamp the maintained name onto `describe().source.provider`. The root
597
+ exports the option-shape combinators and `PROVIDER_COMMON` for provider authors.
598
+ `remoteMcp()` accepts `classify`; its review is still validated by the shared
599
+ classification validator and applied only by the registry. Custom connectors
600
+ remain opaque, preserving `Connector.classification` (#705).
601
+ - Depend on `effect` through `^4.0.0` instead of exactly `4.0.0`, so a
602
+ deployment that also uses Effect resolves one copy. The root still imports
603
+ only the stable `effect` module (#705).
604
+ - **Breaking:** Validate `ConnectaConfig` against one schema that also produces its TypeScript
605
+ type and every default, under one policy: a wrong value refuses to construct,
606
+ naming its path. `execute.*` limits, `discovery.*`, `calls.maxResultBytes`, and
607
+ a connector's own `maxResultBytes` used to warn and fall back to a default;
608
+ they now throw. `connectors`, `auth`, `storage`, `logger`, `publicUrl`,
609
+ `deploymentInfo`, and `serverInfo` are checked by shape too (`storage` must
610
+ implement all five `KVStorage` methods, the same refusal #722 added, now
611
+ reported by the schema);
612
+ `serverInfo.websiteUrl` and a `remoteMcp()` `url` must be http(s). An explicitly
613
+ `undefined` optional value counts as omitted at any depth, and a misspelled
614
+ pool option is reported as `ConnectaConfig.pools.<name>.<key>` with the other
615
+ unknown options (#705).
616
+ - **Breaking:** Both deployment shapes keep their configuration in `src/connecta.config.ts`
617
+ and start it from an entry under 30 lines; optional modules are type-checked
618
+ code switched on by the environment instead of commented blocks. The Node
619
+ template turns Clerk on with both `CLERK_PUBLISHABLE_KEY` and
620
+ `CLERK_SECRET_KEY` (one alone refuses to start), the vault with
621
+ `CONNECTA_CREDENTIAL_KEY`, and activity with `CONNECTA_ACTIVITY=on`, all in its one `CONNECTA_DATABASE` SQLite file;
622
+ it always serves `cta_` access tokens and depends on `@clerk/backend`. The
623
+ Worker example's vault follows the `CREDENTIAL_ENCRYPTION_KEY` secret, and
624
+ activity follows the `CONNECTA_ACTIVITY`
625
+ var (`"on"`), in `CONNECTA_DB`. Activity in both shapes keeps 90 days
626
+ (`retentionDays`), pruned on write (#705).
627
+ - Contributors add changelog fragments in `.changes/`; release authors assemble
628
+ them with a hand-written narrative using `npm run changelog:assemble`.
629
+ Vitest runs every suite on Node and excludes `*.node.test.ts` from Workers,
630
+ with each Node-only reason recorded in the file instead of shared suite lists (#705).
631
+ - Convert the eight hosted MCP implementations to reviewed `defineProvider()` presets. Preserve authentication, endpoint choices, tool classifications, schema digests, results, and API defaults. Add usage prerequisites and pool-trust guidance. Hosted factories now reject blank titles and malformed common options at construction. Remove the internal `withVettedCatalog` helper; public `*_VETTED_CATALOG` exports remain deprecated aliases of each provider definition's classification.
632
+ - Contributors get `npm run check:fast`, a partial inner loop that runs the
633
+ static checks concurrently with Vitest suites related to the branch's changes
634
+ and the always-on package guards; `npm run check` remains the gate. Pull
635
+ requests audit dependencies when root or nested npm manifests, lockfiles,
636
+ shrinkwraps, or configuration change; a nightly workflow and publishing still run `npm audit`. The Chromium
637
+ install retry waits for a leftover apt process instead of failing on its lock,
638
+ and repository skills for providers, spec features, releases, and CI triage
639
+ live in `.claude/skills/` (#705).
640
+ - Give all maintained providers their own folders and generate provider exports, build registrations, package smoke fixtures, conventions, and the README inventory. Preserve provider metadata, guides, registry classifications, public import names, and reviewed bundle caps. Standardize construction errors and report public-contract parser failures per provider, including an explicit manual review record for Breeze.
641
+ - **Breaking:** Make hosted MCP the default for Notion, Vercel and Cloudflare, and remove duplicate named REST operations. `surface: "api"` now selects a documented REST complement. Configure complements under distinct connector ids with their own credentials; hosted OAuth never falls back to an integration token or replays a write. Notion retains every internal-integration capability under explicit `integration_*` names because hosted user OAuth is not equivalent, including headless content writes, full REST pagination and exact JSON blocks. Vercel retains value-safe environment tools and verified deletion/domain gaps; deployment promotion moves to hosted `request_promote`. Cloudflare retains read-only-program reads, byte/header-compatible uploads and legacy Global API Key and R2 jurisdiction mutations; `execute` remains a write. Existing Notion bot workflows must select `surface: "api"` and migrate names to `integration_*`; Cloudflare writes move to hosted `execute` while REST reads can keep their explicit API connector. See `documentation/provider-migration-0.29.md`.
642
+ - **Breaking:** The result stash's byte and entry bounds are booked in one ledger record in
643
+ storage by compare-and-set, so they bound the deployment rather than each
644
+ isolate or process. Every chunk expires by its charge's deadline, concurrent
645
+ stashes retry a lost swap instead of refusing, and a stash write that fails
646
+ releases its charge once it has deleted what it may have written. `engines.node` is `>=22.13.0`, the first
647
+ Node 22 with `node:sqlite` unflagged (#705).
648
+ - **Breaking:** Classify tools once per request-scoped catalog entry, with exact deployment overrides ahead of provider review and downstream annotations. Add read-only and trusted pool tiers, defaulting to read-only, and annotate execute_code as a write on trusted endpoints. Remove connector and execute approval exemptions while preserving write budgets and unknown-outcome accounting. Deployments must explicitly enable trusted pools for program writes.
649
+ - **Breaking:** Discovery returns one flat `{ catalogErrors, tools, total, offset, limit, hasMore }` page from both `search_tools` and `connecta.search`, replacing top-level connector groups. Exact tool names and canonical addresses rank before connector identities, then partial matches. Exact connector ID or title queries browse that connector's tools even when individual descriptions mention its identity. Unknown connector scopes and recognized absent services return an explicit endpoint-scoped absence instead of lookalikes. Catalog credential and permission failures appear before tools with recovery instructions and fixed messages derived only from error codes and connector IDs. Program search and describe default to JSON Schema values; compact schemas and TypeScript signatures are labeled `schemaFormat: "text"`.
650
+ - **Breaking:** Make call errors actionable with agent-only schema keys, enum values, bounds, received types, validated examples and known conditional requirements. List only the current endpoint's configured connectors on address failures. Distinguish host authentication (`host_auth_required`), downstream OAuth (`downstream_oauth_required`) and provider permission (`provider_permission_denied`) recovery. A dispatched write timeout returns `write_outcome_unknown`, never invites an automatic retry, and echoes bounded arguments only to its caller.
651
+
652
+ Decide retryability from HTTP status, registered OAuth codes, typed SDK errors and runtime network facts, never prose (#700). Untyped errors whose text mentions timeout, 429, 502, 503, 504, rate limits, temporary failures or cross-request cancellation are now generic non-retryable failures; typed and structured transport failures retain their verdicts. A 400 registration refusal saying "temporarily unavailable" is non-retryable; 429/502/503/504 refusals remain retryable regardless of body or URL wording. An SDK `RequestTimeout` is a timeout regardless of message wording. An OAuth token endpoint HTTP 403 is now `provider_permission_denied`, keeps the stored grant, and does not start consent. Drop downstream `ProtocolError.data` on errors rethrown by connecta. Operator records remain payload-free (INV-6).
653
+
654
+ Preserve complete error envelopes in MCP structured content and agent-readable text in every direct-call result mode. Maintained API providers distinguish permission refusals from invalid credentials, including Workspace delegation and insufficient scopes, with provider-specific recovery guidance.
655
+ - **Breaking:** Complete the JavaScript guest API with `connecta.result(id, options)` and `connecta.skill(name)`, support object-form calls, and remove the `get_result` meta-tool. Guest calls now return `{ data, format: "json" | "text" }`; direct results also declare their format. Every program reports host-call counts, and guest failures use `program_error` with source locations and fixed repair hints. Uncaught host errors retain their type through executor-owned Error identities and host outcome channels. Programs and direct calls share configurable deadlines and report operation, stage, elapsed time, and effective deadline. Unawaited emission delivers MCP media content; emitted text shares the program result cap. All six meta-tools advertise output schemas. The shared discovery and doctor tool-name set excludes `get_result`, retaining negotiated doctor checks. Spec coverage records tool calls with both address headers and output schemas.
656
+
657
+ Result paging binds stashes to the admitted identity, endpoint, origin, connector, and tool, and rechecks current grants, pool membership, and trust before returning data. Legacy unbound stashes are unavailable after upgrade. Truncated direct writes on read-only pools return inline notices and bounded previews without storing results or advertising unavailable paging. Refresh refusals retain write accounting, including uncertain outcomes.
658
+
659
+ Worker guest modules cannot resolve runner modules through dynamic imports or builtin module loaders. A private runner module captures its references before guest module evaluation, and the adapter refuses splices containing more than one expression before loading. Worker typed failure details stay in a per-run host map; the runner retains only random failure IDs by Error identity, and unknown IDs attach no host details. The Worker codec reads only own binary-envelope properties, and completion captures safe array iteration even without a guest prelude. QuickJS checks interrupt and deadline facts before safely describing rejections without guest getters, serialization hooks, or Proxy traps, including interruptions during private diagnostic initialization. Images require a supported MIME type and canonical base64 before collection. Catalog search advertises the shared flat schema; eval programs and graders use typed data and result paging.
660
+
661
+ Worker startup refuses upstream binary codec changes that would bypass own-property checks. Required-field validation messages retain ordinary JavaScript guidance. Bundle baselines record Acorn for the Worker import router and single-expression validation; the parser remains outside the root import graph.
662
+ - Teach the guest global, positional calls, discovery envelopes, JSON and text results, partial failures, and terminal host-call budgets in a shorter usage guide. Route single known reads and writes directly. Check every JavaScript example on QuickJS and Workers, and keep the tool's API notation aligned with the shipped guest declaration.
663
+ - **Breaking:** Use SDK Client.listTools aggregation and a SQL-backed ResponseCacheStore for complete, intake-redacted remote MCP catalogs. Honor bounded downstream ttlMs and public/private scope within host-computed auth partitions; request tokens and personal auth remain private even under public hints. Partition private entries by admitted principal and pool even with shared auth, preserve original fetch age, and refuse invalid header declarations before SDK logging. Replace the registry's memory/stale/persist cache with request-local reads. Remove discovery.persistCatalog and discovery.staleCatalogSeconds; add catalogMinTtlSeconds and catalogMaxTtlSeconds (defaults 0 and 86400). catalogTtlSeconds remains the legacy fallback. Retired registry catalogs are ignored. Transient first-page tool listing failures may use complete SQL-backed facts in the same auth partition for five minutes after expiry. Expiry provenance survives classification; stale fallback and zero-TTL classifications cannot authorize post-entry auth recovery.
664
+ - Default downstream OAuth to per-connector client metadata documents on public HTTPS deployments, retain DCR fallback, support issuer-bound static remote MCP clients, and report the selected registration path. Basecamp no longer requires an external metadata document.
665
+
666
+ Validate callback state and issuer before interpreting authorization errors, then atomically consume the verified consent and discard its PKCE verifier. Error and code callbacks share one claim; Continue starts a new consent after an error.
667
+
668
+ On Disconnect, remove local authorization first and attempt issuer-bound RFC 7009 revocation once when advertised. Failed revocation reports `oauth_revocation_failed` without provider text or credentials.
669
+
670
+ Register raw confidential client IDs and secrets before token and revocation dispatch, including form-encoded Basic credentials, so downstream echoes remain redacted after refresh.
671
+ - Cache downstream protocol verdicts for five minutes within the connector, credential generation, admitted identity and pool. Modern verdicts skip discovery on later requests. Legacy verdicts skip the probe while retaining a fresh initialize handshake. Automatic negotiation retries a probe HTTP 5xx with a fresh legacy transport.
672
+ - **Breaking:** Support Clerk and Cloudflare Access for inbound human authentication, with Access limited to trusted direct Worker context. Machines use stored `cta_` tokens. Remove `/auth/bearer`, `bearerToken`, and request-selected `assertedPrincipal` identities. Install `accessTokens(storage)`, provision a token per machine or human owner, update actor-id grants, rotate clients, and delete old secrets. Access service credentials satisfy the edge only and need a `cta_` token inside connecta. Replace custom auth `final`/`finalRefusals` with synchronous `recognizesCredential`. Select 401 challenges from the provider that actually serves protected-resource metadata, include Clerk scopes, and remove the Clerk authorization-server metadata proxy. See the exact migration steps in `documentation/auth.md`.
673
+ - **Breaking:** **Downstream OAuth state is one grant record per owner and one flow record per
674
+ consent.** Each OAuth connector's owner has one `oauth:grant` record (epoch,
675
+ the latest consent's state digest, and one authorization server's client,
676
+ tokens, and discovery, sealed as one body with the vault), and each consent
677
+ has an `oauth:flow:<sha256(state)>` record expiring with its link. Every grant
678
+ write is a compare-and-set against the record it read, and Restart and
679
+ Disconnect replace the epoch in one, so the epoch namespaces, cleanup lineage
680
+ (`oauth:generation`, `oauth:cleanup:`, `oauth:cleanup-at:`), grace sweeps, and
681
+ flow-entry grant retirement are gone. A grant holds one server: saving
682
+ discovery, a client, or tokens for another replaces it. The refresh
683
+ coordinator stores an accepted rotation before releasing anyone and hands
684
+ later readers that result; the 503 "previous credentials commit" answer is
685
+ gone. The first read of each owner's grant migrates the 0.28 layout once and
686
+ deletes it: issuer-stamped client, tokens, and discovery carry over (sealed
687
+ values reopen under their old keys); a disconnected connector stays
688
+ disconnected; grants from v0.8.1 and earlier and pending consents do not, so
689
+ reissue pending Connect links after upgrading. Rolling back needs consent
690
+ again. A programmatic `finishAuth` names its consent by the callback's
691
+ `state`; one that names no pending consent is refused before anything is sent
692
+ (#707).
693
+ - **Breaking:** Bind modern MCP client capabilities and identity to request-local meta-tool context; record only allowlisted ASCII client name/version facts in activity, including SQLite and D1 storage. Client names are bounded to 64 characters and versions to 32; invalid values are absent at recording, storage write/read, and operator UI boundaries. Advertise the served extension map and private discovery cache hints, declare direct-call address headers, and make doctor negotiate through the SDK and report its revision using the exported META_TOOL_NAMES set. Connecta-owned 403, pool 404, and deadline 504 refusals now return JSON-RPC error bodies with unknown request IDs omitted. Admission codes change from -31001/-31002 to -33001/-33002; deadline, pool, and access refusal codes are -33003/-33004/-33005.
694
+ - Test operator pages with typed visual fixtures and real-server sign-in, OAuth connect, and credential save/test flows. Generate and compare screenshot baselines on Linux Chromium, with a CI workflow for snapshot updates.
695
+ - Rebuild the operator shell on React, Radix and Tailwind v4 with an Inter token layer, light/dark sidebar, accessible dialogs, tabs, tables and a ⌘K command palette. Serve hashed immutable assets on Node and Workers with same-origin script CSP and generate the asset manifest before build and test. Clerk pages admit its validated loader origin and the exact Cloudflare CAPTCHA host, with Clerk image and worker permissions. Apply common HTML security headers to status and error pages, including browser 404s (#708).
696
+ - Use signed-in Claude Code and Codex CLI logins for agent evals, default to Sonnet 5.5 and GPT-6-Luna, and activate the merged resource-read task.
697
+ Grade record facts together across prose, lists and tables, reject conflicting record facts, and require structured absent-service answers backed by genuine discovery.
698
+ - Adopt oxfmt for repository formatting. Contributors should run `npm run format` before committing; `format:check` runs in the fast and full CI checks.
699
+
700
+ ### Fixed
701
+
702
+ - **Breaking:** Sanitize connector failures at shared request boundaries (#695). Transport, parser and unexpected handler failures expose checked failure facts and fixed descriptions instead of downstream-derived causes or nested errors. Agent-facing downstream refusals retain their classification and pass through request credential redaction. Callers that inspected error causes must use typed details instead.
703
+ - **Breaking:** Make logs, status and activity payload-free (INV-6, #695, #716). Failure records contain checked typed facts instead of messages, stacks or raw downstream error text. `ActivityEventInput.errorCode` is a closed union, and call-failure logs replace `message` with those facts. Status text and tool names follow the same disclosure checks. Downstream body reads use UTF-8 byte decoding, and catalog drift timestamps are validated before disclosure.
704
+ - Return fixed OAuth registration and discovery failure descriptions with checked origin, status, error code and step facts (#695). Google delegated-token failures use the same rule. Downstream response text does not enter these diagnostics; classification and retryability remain intact.
705
+ - **The test gate fails for behavior, not for load.** On a busy machine —
706
+ several `npm run check` runs at once, or a crowded CI runner — suites failed
707
+ with default workers that passed alone, which teaches a rerun-until-green
708
+ habit that hides real regressions
709
+ ([#692](https://github.com/zackbart/connecta/issues/692)). Request
710
+ lifetimes, probe deadlines, refresh claims, and the QuickJS runtime's own
711
+ wall deadline are now tested on a fake clock that moves only when a test
712
+ advances it, each pinned a tick either side of its boundary. A QuickJS pool
713
+ run that outlives its deadline may end at the child's own report or at the
714
+ parent's termination after its fixed grace, and pool cases accept either.
715
+ QuickJS suites not about the guest CPU budget run under a generous one,
716
+ because QuickJS charges that budget by wall clock and a loaded host billed
717
+ its own contention to the guest; the heap-cap case allocates a bounded
718
+ amount, so only an enforced cap fails it, and the event-loop checks derive
719
+ their bound from the guest budget. Real subprocesses, local workerd I/O,
720
+ whole-tree walks, and worst-case payload sweeps keep hang guards sized for a
721
+ loaded host instead of vitest's 5s default. Nothing that ships changes.
722
+ - **Breaking:** Bind downstream OAuth completion to the initiating user for personal and
723
+ shared connectors. Return connecta `/connect` links to agents and operator
724
+ actions; keep status reads passive.
725
+ - Drop downstream tools whose names contain C0, DEL, or C1 control characters at fresh and cached catalog intake. Report only the dropped count in connector drift and keep space and non-ASCII names callable with withheld operator records.
726
+
727
+ Reject control characters in configured static tool names at construction, before startup logs, with a path error that omits the tool name.
728
+ - Keep Clerk SDK upstream error text and workerd native body-reader diagnostics out of operator output. Read all Clerk SDK responses as bytes, route SDK diagnostics through checked failure records, and preserve browser handshake and JWT authentication. Verify response-wrapper fidelity on Node and Workers.
729
+
730
+ The bundled gate client has Connecta-owned declarations for user lookup. The optional Clerk peer accepts compatible 3.x releases independently of the generator's exact internal SDK dependency.
731
+ - Record Clerk authentication and admission denials with checked fixed reason codes. Exclude provider user IDs, email addresses, and email domains from denial logs, including email lookup failures. Add denial canaries for Clerk and Cloudflare Access across logs, activity, and status.
732
+ - Make explicit Authorization headers decisive across inbound auth providers and protected routes. Normalize the case-insensitive Bearer scheme with one shared parser, reject malformed and unsupported headers with a 401 challenge, and exclude ambient cookies, Clerk browser handshake credentials, and Access context. Preserve explicit-header 401 challenges on OAuth starts and callbacks. Keep cookie-only human authentication and stored machine-token ownership.
733
+ - Record undeclared, denied, and throwing pool grants with checked fixed reason codes. Keep identity-provider text out of operator logs and status, while preserving typed activity actors. Cover Clerk OAuth and Access UUID/email pool refusals on Node and Workers. Preserve the operator shell sign-in and CSP contracts after rebasing.
734
+ - Keep downstream public catalog hints within host-computed credential and pool partitions. Request tokens and personal auth always use private cache entries, and OAuth credential changes fence cache reuse even within one epoch. Validate remote resource reads against complete, partitioned advertised resources and URI templates before dispatch; refuse unmatched URIs and encoded traversal with a fixed typed failure. Add a completed SDK catalog refresh hook with previous and new digests for drift-event integration.
735
+ - Keep downstream catalog notifications active for the connection after a successful resource read or listing. Give temporary remote connector instances separate cache lifetimes within the same request. Bind SDK cache I/O and cleanup to each serialized listing's cancellation and generation, let later listings publish a fresh redacted catalog after invalidation, and stop cancelled storage waits from blocking the next listing.
736
+ - Preserve Cloudflare R2 bucket storage-class updates through the REST complement with a validated cf-r2-storage-class header, without requiring jurisdiction for ordinary buckets.
737
+ - **One completed OAuth MCP call no longer aborts its siblings.** A shared
738
+ downstream transport and OAuth provider retained the first call's deadline
739
+ signal, which ends even on success. Subsequent calls and pending parallel
740
+ calls could therefore fail with "The operation was aborted" well before
741
+ their deadlines. The connection now owns that signal; each call keeps its
742
+ own SDK cancellation. A cancelled handshake waiter leaves other waiters
743
+ alone, and the last cancelled waiter still abandons the connection and its
744
+ refresh (#704).
745
+ - Preserve downstream tool definitions in the complete SQL-backed SDK catalog cache. Output-schema validation, `Mcp-Param-*` header mirroring, and refusal of tools requiring task-based execution use those definitions on warm reads as well as after a same-request listing. Registry manifests v2/v3 are retired and refreshed rather than reused (#704).
746
+ - **Tool catalog changes are no longer advertised.** Modern `server/discover`
747
+ and legacy `initialize` report `tools.listChanged: false`, because a fresh
748
+ server per request cannot publish those notifications. Modern
749
+ `subscriptions/listen` requests receive HTTP 404 with JSON-RPC `-32601`
750
+ (Method not found), without acquiring a request-admission permit or opening
751
+ an SSE stream. Authentication and SDK protocol/header validation still run.
752
+ Legacy listens remain unsupported (#704).
753
+ - End `execute_code` at the first host call beyond its budget with one typed
754
+ `budget_exceeded` error, even when the program catches and ignores failures.
755
+ The host suspends the refusing bridge call, closes further host access, and
756
+ releases the sandbox lease. The failure reports payload-free attempted,
757
+ admitted, succeeded, and failed host-call counts (#704).
758
+ - Require consent again for historical OAuth credentials without consistent issuer stamps. The one-shot legacy migration carries only issuer-bound grants and does not adopt unstamped credentials under newly discovered metadata.
759
+ - Keep each OAuth grant bound to one issuer by construction. Flow entry binds the current epoch; reads, writes and consent completion remain fenced to that epoch, so superseded flows cannot change a newer grant or return its consent URL. On the first grant read, the one-shot layout-2 migration preserves consistently issuer-stamped credentials, rejects unstamped or inconsistent credentials, and discards pending consents.
760
+ - Sanitize OAuth token-endpoint failures before they reach the SDK or its console diagnostics. Failures retain the HTTP status, registered OAuth error code and `Retry-After`, with fixed descriptions instead of downstream response text. Invalid code-exchange responses receive the same treatment, and migrated grants retain their issuer stamps.
761
+ - Retry accepted OAuth refresh rotations through bounded grant-commit attempts without sending another refresh request. Exhausted commits require re-consent; waiter deadlines remain retryable `unavailable` failures. Failed credential writes return fixed text without storage error causes.
762
+ - **A token endpoint cannot stamp its own issuer.** A refresh answer carrying
763
+ `issuer` had that field kept when connecta persisted a rotation for a
764
+ cancelled caller with no issuer to stamp. Only the client binds a grant, so
765
+ the field is dropped, as the SDK drops it.
766
+ - Make the optional-module probe deadline test deterministic by waiting for its timer before advancing fake time and awaiting response settlement at the deadline. Preserve the deadline boundary and verify connector scope cleanup.
767
+ - Provider description stamping preserves connector prototypes and inherited
768
+ classification. It defines `describe()` in place when possible, keeping class
769
+ method receivers, and otherwise inherits through a wrapper with only its own
770
+ `describe()` property. Discovery and calls retain the reviewed classification.
771
+ - Changelog assembly requires committed inputs and prints the git restore command
772
+ for recovery after a failure or interruption. The suite guard checks Vitest's
773
+ actual Node and Workers collection, including excluded directories and symlink
774
+ targets (#705).
775
+ - Fence late downstream catalog publications when the SDK receives tools/list_changed, including first cache refreshes. Stop subsequent cache storage operations after cancellation or scope teardown, preserving complete catalogs and ordinary public/private cache hits.
776
+ - Keep core-protocol JSON-RPC refusals, server discovery, registered meta-tool results, and both MCP transports behind the request-scoped credential-echo redaction boundary. Modern direct-call security tests send the required mirrored address header and keep their credential-echo assertions.
777
+ - Complete dispatched downstream OAuth refreshes after caller cancellation and retain their grant commits through Workers waitUntil. Permanently refuse refresh fingerprints after ambiguous network, response-body, or deadline outcomes so later requests and isolates require re-consent without resending the token.
778
+ - Fence downstream OAuth refresh dispatch with a durable CAS state and a 20-second HTTP deadline. Expired unsent claims can be taken over; ambiguous dispatched tokens require re-consent and are never resent. Waiter timeouts, including blocked storage reads and local joiners, return retryable unavailable while keeping the grant. SQLite and D1 create and check TTLs with database time so isolate clock skew cannot expire a live holder.
779
+ - Block outstanding and ambiguous downstream OAuth refresh fingerprints across Restart and re-consent. A resolved fingerprint can be used once in a later epoch only when the code exchange observed its resolution before completing. Preserve pending holder liveness across resets and CAS-guard every resolution transition.
780
+ - Record downstream OAuth refresh-token fingerprints before dispatch with outstanding, ambiguous, and resolved state across grant epochs. Retry valid rotation commits without another HTTP request and require re-consent after every dispatched failure or exhausted commit. Waiter deadlines remain retryable without permitting token replay within the epoch or while the outcome remains outstanding or ambiguous.
781
+ - Bind downstream OAuth consents to their issuer, client, token endpoint, and discovery; advance the epoch when the issuer changes. Coordinate refresh redemption across isolates with shared CAS leases. Retry layout-2 credential cleanup after interrupted migration or reset, and require callback state for both OAuth adapters.
782
+ - **A downstream OAuth callback can no longer redeem its code after a restart,
783
+ and a duplicate can no longer destroy the grant another completed.** The
784
+ exchange claims its consent by compare-and-set after every read it depends
785
+ on, then re-reads the epoch and sends the code in the reaction to that read:
786
+ of duplicate callbacks exactly one sends, the rest get the flat 400 for an
787
+ already-used link, and a restart published before the send fails the callback
788
+ with nothing sent. A refused code invalidates only the client or tokens the
789
+ exchange began with, and consumption is recorded on that consent alone, so a
790
+ delayed duplicate never deletes or invalidates a newer consent Continue
791
+ published (#697). A forced restart still reuses an issuer-bound registration
792
+ and recovers from a refused one by registering again (#611).
793
+ - Preserve discrete catalog-change activity after SDK cache refreshes. Compare hash-only baselines per host-computed cache partition, retain request, owner, pool and deferred-write attribution, and disclose private catalog changes only to their admitted owner. Initial, identical, rejected and abandoned catalogs emit no change event.
794
+ - Match advertised resource URI templates with deterministic forward scans. Refuse ambiguous expression boundaries with resource_template_ambiguous and cap each read at 262144 template-plus-URI characters with resource_match_budget_exceeded. Operator diagnostics retain each typed refusal once without URI or template text. Keep scheme and authority literal, reject Unicode controls and format characters at every decoding layer, and allow safe multi-segment reserved and exploded paths.
795
+ - Show empty credential slots as credential_required. Permit Activity reads for authenticated non-interactive principals explicitly granted activityAccess, with connector, tool, personal-owner and pool disclosure checks. Record call-time classification, result byte counts and discrete catalog changes in nullable activity columns, retaining request grouping and legacy rows.
796
+ - Correct agent eval destination aliases and final-answer evidence grading, record runner limitations as typed N/A skips, and support offline regrading with complete saved grading inputs for future trials. Reject contradictory requested record facts and require a structured refusal outcome, and grade simulated URL-auth behavior for both runners. Commit sanitized final live Sonnet 5.5 and GPT-6-Luna baselines with per-task scores, concrete failure triage and the legacy join prompt ambiguity.
797
+ - Mark failed downstream input continuations as not retryable once their single-use nonce is consumed. Read failures without existing recovery prerequisites direct a fresh input round through the original call. Write reconciliation and auth recovery guidance stay unchanged; spent continuations must not be resent. Typed failure codes and first-call retryability are preserved.
798
+ - Classify a reviewed read as a write when its live schema digest changes or cannot be checked (#705). `call_tool` refuses it toward `call_destructive_tool`; `execute_code` refuses it before dispatch in read-only pools, while trusted programs may dispatch it. Legacy discovery filters such as `approval-required` select write classification, not host approval policy. Digests cover the whole schema, and oversized unchecked schemas fail closed. Request-local entries classify intake-redacted facts using the running release's review and deployment overrides. Complete SQL-backed SDK caches retain facts, not classification verdicts; retired registry catalogs are refreshed instead of preserving old read claims.
799
+ - Storage keys and list prefixes reject U+0000 (NUL) with `TypeError` on D1,
800
+ SQLite, and memory storage before any storage access. Key builders reject
801
+ unencoded NUL components, and state-file migration validates every key before
802
+ importing. This prevents Node 22's `node:sqlite` TEXT results from truncating
803
+ keys at NUL while preserving the existing `connecta_kv` TEXT table (#705).
804
+ - Use plain positional SQL parameters with values bound in statement order on
805
+ both storage drivers. Node's SQLite driver prepares the shared SQL unchanged,
806
+ without rewriting quoted text, on every supported Node version (#705).
807
+
808
+ ### Removed
809
+
810
+ - **Breaking:** Workers KV, `fileStorage` from `@zackbart/connecta/node`, and the Worker
811
+ example's copied `cloudflare-kv.ts`, `d1-storage.ts`, `d1-activity.ts`, and
812
+ `d1-activity-row.ts` are removed, as is the Node template's
813
+ `src/file-activity.ts`. `KVStorage.list` and `KVStorage.compareAndSet` are
814
+ required, and `createConnecta` refuses storage missing either at construction.
815
+ Every fallback for stores without compare-and-set is gone: downstream OAuth
816
+ grant discard, stale-write cleanup, and generation fencing; OAuth handoffs and
817
+ single-use connect links; and access-token rename and revocation.
818
+ The Worker example binds one D1 database, `CONNECTA_DB`; the Node template
819
+ keeps everything, activity included, in `CONNECTA_DATABASE` (default
820
+ `./.connecta.sqlite`, `/data/connecta.sqlite` in the container), replacing
821
+ `CONNECTA_STATE_FILE` and `CONNECTA_ACTIVITY_FILE` (#705).
822
+ - **Breaking:** Remove catalog drift counts from `/health`, `connecta doctor`, and activity. Remove the old optional drift-count channel and its event type. Keep discrete catalog-change events in the ordinary activity timeline. Authenticated connector status and the maintainer-run provider check retain drift observations.
823
+ - **Breaking:** Remove built-in artifacts: the `connecta/artifacts` subpath, `artifacts:` config
824
+ slot and `artifactOrigin`, operator pages and navigation, scheduled refresh,
825
+ and agent tools and guide. Publishing belongs in dedicated services.
826
+
827
+ Delete the `connecta/artifacts` import and `artifacts:` configuration, plus
828
+ `artifactOrigin` if set; a configuration that still contains either key fails
829
+ at construction with a `ConfigError`. Remove `CONNECTA_ARTIFACTS` and refresh timers or cron
830
+ handlers. Dedicated artifact bindings or storage can optionally be dropped.
831
+ Existing artifact data is left untouched and ignored; nothing migrates, reads,
832
+ or deletes it as part of this removal (#709).
833
+
834
+ ### Security
835
+
836
+ - Bind GitHub repository grants to repository IDs and narrow installation tokens with `repository_ids`. Renames retain their grants; transfers and replacement repositories at reused names cannot inherit cached access. Verify discovery and search repository IDs before returning data.
837
+ - Collect sent credentials across every connector context in one upstream request
838
+ and redact meta-tool, guest bridge, and serialized MCP outputs at one shared
839
+ boundary. Redact failed catalog listings before shared refresh publication and
840
+ keep catalogs and result stashes sanitized before caching.
841
+ - Refuse credential echoes in normalized downstream OAuth metadata and saved authorization URLs, and restrict operator failure origins. Use an eight-character floor for all echo matching and warn once when configured secrets are below it.
842
+ - Resource URI template matches now re-check the final authority, host interpretation, and path. Matching refuses Unicode separators, URI syntax lookalikes, and dot segments while preserving encoded-percent literals, ordinary percent-encoded UTF-8 path segments, and bounded matching.
843
+ - Redact credentials used by a call from agent-facing downstream diagnostics,
844
+ tool results, and nested errors across all connectors and guest program calls.
845
+ The per-call secret set stays in memory and covers credential slots, auth
846
+ headers, bearer tokens, URL encodings, and base64 forms. Register final outgoing
847
+ requests, including auxiliary OAuth headers and query credentials. Redact mixed
848
+ JSON escapes and joined text blocks after unwrapping and before paging, emits,
849
+ or program returns. Cache one matcher per secret set. Match
850
+ credentials of at least eight characters to avoid corrupting ordinary text with
851
+ short Basic usernames; Connecta's own messages never quote credential values.
852
+ - Register credentials sent during remote MCP discovery and retries, and redact every catalog metadata string before retaining definitions or caching catalogs. Refuse the complete catalog when redaction would change a tool name, preserving dispatch names and catalog completeness.
853
+ - Refuse redirects for credential-bearing OAuth token requests in remote MCP and static API connectors. A redirected refresh requires re-consent without another send. Keep spent fingerprints across grant epochs. A new consent can use a byte-identical refresh token only after the earlier dispatch resolved and the consent observed that resolution.
854
+
5
855
  ## 0.28.1 — 2026-10-05
6
856
 
7
857
  This patch adds six maintained connections for church operations and the
@@ -1653,7 +2503,7 @@ residue from #179, and the v1 persisted-catalog reader, unreachable since
1653
2503
  - **Package and docs.** Six finished design records moved to
1654
2504
  [`records/`](https://github.com/zackbart/connecta/tree/main/records), outside
1655
2505
  the published files, and packed-link validation folded into the documentation
1656
- checker: −1,342 shipped lines, −91 KB (#464). `ethos.md` is the constitution
2506
+ checker: −1,342 shipped lines, −91 KB (#464). the ethos is the constitution
1657
2507
  again — refusals, invariants, and what this is, at 1,188 words instead of
1658
2508
  3,174, with every decision intact and a word cap replacing the line cap
1659
2509
  (#471).
@@ -3199,7 +4049,7 @@ the new text is the entire release (#282, PR #284).
3199
4049
  says to fetch first and check the shape in code, and on a surprise — empty
3200
4050
  array, missing key — to return a trimmed first record instead of rendering.
3201
4051
  The wrong view becomes the sample you needed.
3202
- - **Result sampling on the catalog surface is refused.** The `ethos.md`
4052
+ - **Result sampling on the catalog surface is refused.** The former ethos
3203
4053
  decisions table records `sample` / `dryRun` as refused: sampling is execution
3204
4054
  and cannot ride a catalog read, most tools carry required arguments no
3205
4055
  sampler can invent, and a program that checks the shape before rendering
@@ -3556,7 +4406,7 @@ nine tools with the same descriptions and the same instructions as before, byte
3556
4406
  for byte. `surface: "classic"` alongside an executor restores the ten-tool shape.
3557
4407
 
3558
4408
  The default was flipped by owner decision for a single-operator deployment rather
3559
- than by the repeated per-model eval that [`ethos.md`](./ethos.md) had gated it on;
4409
+ than by the repeated per-model eval that [former ethos](https://github.com/zackbart/connecta/blob/main/decisions/0001-ethos-verdict-table.md) had gated it on;
3560
4410
  that row now records the decision and its reasoning, and
3561
4411
  [`eval/code-first-gate`](./eval/code-first-gate/README.md) remains as measurement
3562
4412
  with all three of its arms now real deployment shapes rather than harness
@@ -3821,13 +4671,13 @@ optional property checks, and an automated Workers test-partition guard. A
3821
4671
  deployment can take this release without touching its config: entrypoints,
3822
4672
  options, storage formats, and the tool surface are unchanged. The docs were
3823
4673
  restructured — the old numbered manual is retired in favor of a terse
3824
- [`ethos.md`](./ethos.md) and stub guides in `documentation/` — and the
4674
+ [former ethos](https://github.com/zackbart/connecta/blob/main/decisions/0001-ethos-verdict-table.md) and stub guides in `documentation/` — and the
3825
4675
  temporary `@hono/node-server` override is gone now that the SDK (1.30.0)
3826
4676
  depends on a patched version upstream.
3827
4677
 
3828
4678
  ### Added
3829
4679
 
3830
- - **`ethos.md`** — what connecta is, what it refuses to be, a decisions table,
4680
+ - **The former ethos document** — what connecta is, what it refuses to be, a decisions table,
3831
4681
  and the invariants, each enforced or reviewer-owned (#175, PR #180). CI caps
3832
4682
  its length; terseness is the point.
3833
4683
  - **Route-contract and operator-UI browser tests** pinning server behavior