@zackbart/connecta 0.34.1 → 0.35.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 (429) hide show
  1. package/CHANGELOG.md +363 -0
  2. package/README.md +62 -106
  3. package/SECURITY.md +3 -2
  4. package/bin/connecta.mjs +41 -16
  5. package/bin/version.mjs +1 -1
  6. package/dist/access-tokens.d.ts +2 -1
  7. package/dist/access-tokens.js +66 -30
  8. package/dist/activity.d.ts +1 -1
  9. package/dist/activity.js +2 -0
  10. package/dist/agent-config.d.ts +23 -0
  11. package/dist/agent-config.js +59 -0
  12. package/dist/agent-description.js +16 -0
  13. package/dist/agent-identity.js +4 -4
  14. package/dist/agent-observation.js +39 -0
  15. package/dist/auth/clerk-sdk/client.d.ts +2 -0
  16. package/dist/auth/clerk-sdk/generated.js +554 -563
  17. package/dist/auth/clerk-transport.js +70 -3
  18. package/dist/auth/clerk.d.ts +13 -0
  19. package/dist/auth/clerk.js +653 -246
  20. package/dist/auth/cloudflare-access.js +46 -42
  21. package/dist/auth/downstream-client-metadata.js +2 -2
  22. package/dist/auth/downstream-oauth.js +2051 -1160
  23. package/dist/auth/downstream-revocation.js +88 -88
  24. package/dist/auth/native-call.js +20 -0
  25. package/dist/auth/native.js +184 -0
  26. package/dist/auth/oauth-start-reset.js +11 -5
  27. package/dist/auth/oauth-token-response.js +121 -102
  28. package/dist/auth/oauth-v2-migration.js +97 -78
  29. package/dist/auth/renewable-grant.js +10 -0
  30. package/dist/auth/static-oauth.js +663 -271
  31. package/dist/auth/vercel-agent.js +266 -0
  32. package/dist/auth-admission.js +204 -0
  33. package/dist/auth-elicitation.js +350 -241
  34. package/dist/authorization-handoff.js +164 -90
  35. package/dist/call-admission-context.js +3 -10
  36. package/dist/call-admission.js +2 -0
  37. package/dist/call-classification.js +9 -0
  38. package/dist/call-recovery.js +32 -13
  39. package/dist/canonical-json.js +8 -0
  40. package/dist/catalog-cache.js +369 -213
  41. package/dist/catalog-service.js +659 -530
  42. package/dist/catalog.js +5 -1
  43. package/dist/concurrency.js +0 -25
  44. package/dist/config-defaults.d.ts +1 -0
  45. package/dist/config-defaults.js +1 -0
  46. package/dist/config-schema.d.ts +3 -1
  47. package/dist/config-schema.js +13 -3
  48. package/dist/config.d.ts +40 -6
  49. package/dist/config.js +40 -26
  50. package/dist/connector-caller.d.ts +4 -0
  51. package/dist/connector-caller.js +17 -4
  52. package/dist/connectors/api-connector.d.ts +12 -7
  53. package/dist/connectors/api-connector.js +142 -56
  54. package/dist/connectors/guarded-fetch.d.ts +51 -17
  55. package/dist/connectors/guarded-fetch.js +215 -121
  56. package/dist/connectors/negotiation-cache.js +20 -10
  57. package/dist/connectors/remote-mcp.d.ts +10 -30
  58. package/dist/connectors/remote-mcp.js +955 -563
  59. package/dist/d1.js +10 -243
  60. package/dist/derived-context.js +5 -0
  61. package/dist/describe-config.d.ts +3 -1
  62. package/dist/describe-config.js +2 -0
  63. package/dist/downstream-input.js +347 -260
  64. package/dist/errors.d.ts +87 -6
  65. package/dist/errors.js +114 -4
  66. package/dist/execute.js +87 -50
  67. package/dist/executor-admission.js +3 -0
  68. package/dist/executors/quickjs.js +163 -112
  69. package/dist/guarded-transport.js +50 -0
  70. package/dist/guest-runtime.js +1 -1
  71. package/dist/http/client.js +439 -0
  72. package/dist/http/errors.js +558 -0
  73. package/dist/http/fields.js +49 -0
  74. package/dist/http/hatch-paging.js +318 -0
  75. package/dist/http/hatch.js +1279 -0
  76. package/dist/http/json-schema.js +267 -0
  77. package/dist/http/node-transport.js +241 -0
  78. package/dist/http/oauth.js +162 -0
  79. package/dist/http/paging.js +228 -0
  80. package/dist/http/provider.js +221 -0
  81. package/dist/http/safety.js +82 -0
  82. package/dist/http/services.js +79 -0
  83. package/dist/http/tools.js +383 -0
  84. package/dist/http/vendor-index.js +138 -0
  85. package/dist/http/vendor.js +448 -0
  86. package/dist/http/write-request.js +66 -0
  87. package/dist/http-policy.d.ts +11 -0
  88. package/dist/http-policy.js +45 -0
  89. package/dist/inbound-credential.js +1 -1
  90. package/dist/index.d.ts +2 -2
  91. package/dist/index.js +4 -0
  92. package/dist/invocation-auth.js +30 -19
  93. package/dist/invocation.js +218 -85
  94. package/dist/mcp-result.js +0 -4
  95. package/dist/membership.d.ts +4 -0
  96. package/dist/meta-output.js +1 -1
  97. package/dist/meta-tools.js +531 -406
  98. package/dist/native-recovery.js +52 -0
  99. package/dist/native-tools.js +382 -0
  100. package/dist/node.d.ts +4 -3
  101. package/dist/node.js +45 -8
  102. package/dist/oauth-handoff.js +177 -104
  103. package/dist/oauth-partition.js +6 -20
  104. package/dist/oauth-sealing.js +6 -7
  105. package/dist/operation-vocabulary.js +101 -0
  106. package/dist/operator-record.d.ts +2 -2
  107. package/dist/operator-record.js +17 -4
  108. package/dist/operator-ui/agents.d.ts +5 -4
  109. package/dist/operator-ui/agents.js +17 -7
  110. package/dist/operator-ui/config-summary.js +5 -4
  111. package/dist/operator-ui/connection-map.js +7 -5
  112. package/dist/operator-ui/contract.d.ts +7 -3
  113. package/dist/operator-ui/endpoints.js +4 -13
  114. package/dist/operator-ui/fix-prompts.d.ts +1 -1
  115. package/dist/operator-ui/fix-prompts.js +0 -8
  116. package/dist/operator-ui/generated.js +3 -3
  117. package/dist/operator-ui/model.d.ts +2 -3
  118. package/dist/operator-ui/overview.js +5 -2
  119. package/dist/operator-ui/setup-commands.js +0 -4
  120. package/dist/operator-ui/view.d.ts +2 -25
  121. package/dist/operator-ui/view.js +2 -76
  122. package/dist/program-writes.js +47 -26
  123. package/dist/provider-call-facts.d.ts +115 -0
  124. package/dist/provider-call-facts.js +182 -0
  125. package/dist/provider-call.d.ts +112 -0
  126. package/dist/provider-call.js +271 -0
  127. package/dist/provider.d.ts +65 -31
  128. package/dist/provider.js +116 -39
  129. package/dist/providers/_shared/google/hatch.js +91 -0
  130. package/dist/providers/_shared/google/kit.js +537 -0
  131. package/dist/providers/_shared/google/result-size.js +15 -2
  132. package/dist/providers/_shared/google/service-account.d.ts +2 -1
  133. package/dist/providers/_shared/google/service-account.js +8 -4
  134. package/dist/providers/_shared/google/workspace.d.ts +6 -210
  135. package/dist/providers/_shared/google/workspace.js +6 -425
  136. package/dist/providers/_shared/rest/compact-index.js +86 -0
  137. package/dist/providers/_shared/rest/dispatch.js +3 -23
  138. package/dist/providers/_shared/rest/kit.js +546 -0
  139. package/dist/providers/_shared/rest/operation-index.js +230 -14
  140. package/dist/providers/_shared/rest/response-media.js +11 -0
  141. package/dist/providers/_shared/rest/tools.js +101 -78
  142. package/dist/providers/_shared/rest/value-safety.js +353 -195
  143. package/dist/providers/_shared/sensitive-fields.js +27 -0
  144. package/dist/providers/breeze/api.js +584 -0
  145. package/dist/providers/breeze/index.d.ts +1 -3
  146. package/dist/providers/breeze/index.js +67 -1420
  147. package/dist/providers/breeze/openapi.generated.js +60 -0
  148. package/dist/providers/breeze/skill.generated.js +2 -6
  149. package/dist/providers/breeze/tools.js +696 -0
  150. package/dist/providers/ccb/api.js +496 -0
  151. package/dist/providers/ccb/index.d.ts +15 -37
  152. package/dist/providers/ccb/index.js +77 -1702
  153. package/dist/providers/ccb/lib/constants.d.ts +34 -0
  154. package/dist/providers/ccb/lib/constants.js +74 -0
  155. package/dist/providers/ccb/lib/guide.js +9 -0
  156. package/dist/providers/ccb/lib/verdicts.js +387 -0
  157. package/dist/providers/ccb/openapi.generated.js +397 -0
  158. package/dist/providers/ccb/safety.js +295 -0
  159. package/dist/providers/ccb/skill.generated.js +2 -6
  160. package/dist/providers/ccb/tools.js +853 -0
  161. package/dist/providers/cloudflare/api.js +1066 -0
  162. package/dist/providers/cloudflare/index.d.ts +5 -22
  163. package/dist/providers/cloudflare/index.js +79 -197
  164. package/dist/providers/cloudflare/lib/config.d.ts +16 -0
  165. package/dist/providers/cloudflare/lib/config.js +2 -0
  166. package/dist/providers/cloudflare/lib/discovery.js +29 -0
  167. package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
  168. package/dist/providers/cloudflare/lib/hatch.js +103 -0
  169. package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
  170. package/dist/providers/cloudflare/lib/response-media.js +75 -0
  171. package/dist/providers/cloudflare/openapi.generated.js +4 -3
  172. package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
  173. package/dist/providers/cloudflare/skill.generated.js +2 -10
  174. package/dist/providers/cloudflare/tools.js +360 -0
  175. package/dist/providers/docs/api.js +51 -0
  176. package/dist/providers/docs/index.d.ts +1 -12
  177. package/dist/providers/docs/index.js +53 -1165
  178. package/dist/providers/docs/lib/constants.d.ts +18 -0
  179. package/dist/providers/docs/lib/constants.js +18 -0
  180. package/dist/providers/docs/lib/discovery.js +26 -0
  181. package/dist/providers/docs/lib/requests.js +48 -0
  182. package/dist/providers/docs/lib/schemas.js +168 -0
  183. package/dist/providers/docs/openapi.generated.js +16 -0
  184. package/dist/providers/docs/safety.js +54 -0
  185. package/dist/providers/docs/skill.generated.js +2 -6
  186. package/dist/providers/docs/tools.js +802 -0
  187. package/dist/providers/drive/api.js +177 -0
  188. package/dist/providers/drive/index.d.ts +1 -12
  189. package/dist/providers/drive/index.js +38 -1448
  190. package/dist/providers/drive/lib/constants.d.ts +6 -0
  191. package/dist/providers/drive/lib/constants.js +6 -0
  192. package/dist/providers/drive/lib/contracts.js +532 -0
  193. package/dist/providers/drive/lib/hatch.js +197 -0
  194. package/dist/providers/drive/openapi.generated.js +77 -0
  195. package/dist/providers/drive/safety.js +90 -0
  196. package/dist/providers/drive/skill.generated.js +2 -6
  197. package/dist/providers/drive/tools.js +1290 -0
  198. package/dist/providers/forms/api.js +71 -0
  199. package/dist/providers/forms/index.d.ts +1 -12
  200. package/dist/providers/forms/index.js +49 -1145
  201. package/dist/providers/forms/lib/constants.d.ts +14 -0
  202. package/dist/providers/forms/lib/constants.js +19 -0
  203. package/dist/providers/forms/lib/hatch.js +72 -0
  204. package/dist/providers/forms/openapi.generated.js +23 -0
  205. package/dist/providers/forms/safety.js +34 -0
  206. package/dist/providers/forms/skill.generated.js +2 -8
  207. package/dist/providers/forms/tools.js +1024 -0
  208. package/dist/providers/github/api.js +150 -0
  209. package/dist/providers/github/index.d.ts +4 -5
  210. package/dist/providers/github/index.js +30 -87
  211. package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
  212. package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
  213. package/dist/providers/github/lib/discovery.js +132 -0
  214. package/dist/providers/github/lib/operations.js +58 -0
  215. package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
  216. package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
  217. package/dist/providers/github/openapi.generated.js +1216 -0
  218. package/dist/providers/github/safety.js +216 -0
  219. package/dist/providers/github/skill.generated.js +2 -5
  220. package/dist/providers/github/tools.js +506 -0
  221. package/dist/providers/gmail/api.js +114 -0
  222. package/dist/providers/gmail/index.d.ts +1 -12
  223. package/dist/providers/gmail/index.js +73 -1621
  224. package/dist/providers/gmail/lib/constants.d.ts +13 -0
  225. package/dist/providers/gmail/lib/constants.js +16 -0
  226. package/dist/providers/gmail/lib/index.js +173 -0
  227. package/dist/providers/gmail/lib/schemas.js +176 -0
  228. package/dist/providers/gmail/lib/threading.js +120 -0
  229. package/dist/providers/gmail/openapi.generated.js +92 -0
  230. package/dist/providers/gmail/safety.js +21 -0
  231. package/dist/providers/gmail/skill.generated.js +2 -6
  232. package/dist/providers/gmail/tools.js +1435 -0
  233. package/dist/providers/overflow/api.js +1008 -0
  234. package/dist/providers/overflow/index.d.ts +1 -17
  235. package/dist/providers/overflow/index.js +60 -1488
  236. package/dist/providers/overflow/skill.generated.js +2 -5
  237. package/dist/providers/overflow/tools.js +873 -0
  238. package/dist/providers/planning-center/api.js +467 -0
  239. package/dist/providers/planning-center/index.d.ts +4 -35
  240. package/dist/providers/planning-center/index.js +69 -2445
  241. package/dist/providers/planning-center/lib/constants.d.ts +58 -0
  242. package/dist/providers/planning-center/lib/constants.js +83 -0
  243. package/dist/providers/planning-center/lib/discovery.js +40 -0
  244. package/dist/providers/planning-center/lib/guide.js +39 -0
  245. package/dist/providers/planning-center/lib/json-api.js +83 -0
  246. package/dist/providers/planning-center/lib/verdicts.js +1758 -0
  247. package/dist/providers/planning-center/openapi.generated.js +1764 -0
  248. package/dist/providers/planning-center/safety.js +188 -0
  249. package/dist/providers/planning-center/skill.generated.js +2 -27
  250. package/dist/providers/planning-center/tools.js +1010 -0
  251. package/dist/providers/sheets/api.js +133 -0
  252. package/dist/providers/sheets/index.d.ts +4 -16
  253. package/dist/providers/sheets/index.js +61 -1172
  254. package/dist/providers/sheets/lib/constants.d.ts +14 -0
  255. package/dist/providers/sheets/lib/constants.js +14 -0
  256. package/dist/providers/sheets/lib/hatch.js +163 -0
  257. package/dist/providers/sheets/lib/requests.js +105 -0
  258. package/dist/providers/sheets/lib/schemas.js +138 -0
  259. package/dist/providers/sheets/openapi.generated.js +30 -0
  260. package/dist/providers/sheets/safety.js +27 -0
  261. package/dist/providers/sheets/skill.generated.js +2 -7
  262. package/dist/providers/sheets/tools.js +734 -0
  263. package/dist/providers/slides/api.js +147 -0
  264. package/dist/providers/slides/index.d.ts +2 -10
  265. package/dist/providers/slides/index.js +48 -2555
  266. package/dist/providers/slides/lib/constants.d.ts +11 -0
  267. package/dist/providers/slides/lib/constants.js +11 -0
  268. package/dist/providers/slides/lib/requests.js +60 -0
  269. package/dist/providers/slides/lib/schemas.js +565 -0
  270. package/dist/providers/slides/openapi.generated.js +18 -0
  271. package/dist/providers/slides/skill.generated.js +2 -6
  272. package/dist/providers/slides/tools.js +1968 -0
  273. package/dist/providers/stripe/api.js +401 -0
  274. package/dist/providers/stripe/index.d.ts +5 -27
  275. package/dist/providers/stripe/index.js +74 -210
  276. package/dist/providers/stripe/lib/config.d.ts +4 -0
  277. package/dist/providers/stripe/lib/config.js +2 -0
  278. package/dist/providers/stripe/openapi.generated.js +4 -3
  279. package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
  280. package/dist/providers/stripe/skill.generated.js +2 -11
  281. package/dist/providers/stripe/tools.js +77 -0
  282. package/dist/providers/tithely/api.js +531 -0
  283. package/dist/providers/tithely/index.d.ts +17 -0
  284. package/dist/providers/tithely/index.js +86 -965
  285. package/dist/providers/tithely/skill.generated.js +2 -5
  286. package/dist/providers/tithely/tools.js +426 -0
  287. package/dist/providers/vercel/api.js +551 -0
  288. package/dist/providers/vercel/index.d.ts +9 -25
  289. package/dist/providers/vercel/index.js +66 -260
  290. package/dist/providers/vercel/lib/config.d.ts +2 -0
  291. package/dist/providers/vercel/lib/config.js +2 -0
  292. package/dist/providers/vercel/lib/index.js +117 -0
  293. package/dist/providers/vercel/lib/logs.js +100 -0
  294. package/dist/providers/vercel/lib/pin.js +270 -0
  295. package/dist/providers/vercel/openapi.generated.js +10 -5
  296. package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
  297. package/dist/providers/vercel/skill.generated.js +2 -7
  298. package/dist/providers/vercel/tools.js +642 -0
  299. package/dist/redaction-identity.js +47 -0
  300. package/dist/redaction-marker.js +125 -0
  301. package/dist/registry.d.ts +21 -15
  302. package/dist/registry.js +409 -376
  303. package/dist/routes/access-tokens.js +1 -1
  304. package/dist/routes/actor-labels.js +1 -1
  305. package/dist/routes/connect.js +1 -1
  306. package/dist/routes/dispatch.js +103 -0
  307. package/dist/routes/mcp.js +170 -79
  308. package/dist/routes/oauth.js +3 -1
  309. package/dist/routes/operator.js +2 -2
  310. package/dist/routes/shared.d.ts +5 -4
  311. package/dist/routes/shared.js +244 -177
  312. package/dist/routes/skills.js +10 -8
  313. package/dist/routes/ui-config.js +27 -3
  314. package/dist/routes/ui-data.js +21 -20
  315. package/dist/routes/ui.js +1 -1
  316. package/dist/runtime/call-admission.js +0 -26
  317. package/dist/runtime/call-entry.js +30 -0
  318. package/dist/runtime/call.js +556 -0
  319. package/dist/runtime/connector-scope.js +7 -0
  320. package/dist/runtime/credential-probe.js +34 -0
  321. package/dist/runtime/executor.js +40 -0
  322. package/dist/runtime/native-storage.js +63 -0
  323. package/dist/runtime/registry.js +27 -0
  324. package/dist/runtime/request-scope.js +72 -0
  325. package/dist/runtime/run.js +15 -7
  326. package/dist/runtime/shared-read.js +34 -5
  327. package/dist/runtime/storage.js +34 -21
  328. package/dist/sent-secrets.d.ts +194 -37
  329. package/dist/sent-secrets.js +1743 -340
  330. package/dist/server.js +5 -56
  331. package/dist/skills.js +322 -222
  332. package/dist/sqlite.js +15 -14
  333. package/dist/storage/d1-copy.js +255 -0
  334. package/dist/storage/d1-driver.js +15 -0
  335. package/dist/storage/keys.js +28 -0
  336. package/dist/storage/sql-driver.js +88 -0
  337. package/dist/storage/sql-native.js +544 -0
  338. package/dist/storage/sql-schema.d.ts +6 -0
  339. package/dist/storage/sql-schema.js +15 -0
  340. package/dist/storage/sql.d.ts +14 -20
  341. package/dist/storage/sql.js +29 -535
  342. package/dist/timeout.js +0 -26
  343. package/dist/tool-safety.js +20 -10
  344. package/dist/types.d.ts +7 -109
  345. package/dist/upward.js +3 -0
  346. package/dist/usage-guide.js +3 -1
  347. package/dist/validate.d.ts +4 -2
  348. package/dist/validate.js +86 -9
  349. package/dist/version.d.ts +1 -1
  350. package/dist/version.js +1 -1
  351. package/dist/worker.js +312 -292
  352. package/dist/workspace-subject.js +5 -1
  353. package/dist/write-outcome.js +454 -34
  354. package/documentation/README.md +27 -7
  355. package/documentation/architecture.md +2547 -226
  356. package/documentation/auth.md +821 -98
  357. package/documentation/code-mode.md +49 -16
  358. package/documentation/deploying.md +164 -20
  359. package/documentation/hosted-mcp-migration.md +460 -0
  360. package/documentation/integrating.md +129 -144
  361. package/documentation/meta-tools.md +409 -32
  362. package/documentation/operating.md +27 -3
  363. package/documentation/operator-tests.md +3 -0
  364. package/documentation/operator-ui.md +68 -45
  365. package/documentation/provider-auth-migration.md +16 -63
  366. package/documentation/provider-migration-0.29.md +10 -5
  367. package/documentation/provider-migration-0.35.md +975 -0
  368. package/documentation/providers.md +965 -0
  369. package/examples/worker/AGENTS.md +23 -9
  370. package/examples/worker/README.md +254 -135
  371. package/examples/worker/alchemy.run.ts +116 -0
  372. package/examples/worker/gitignore +10 -0
  373. package/examples/worker/src/connecta.config.ts +23 -21
  374. package/examples/worker/wrangler.jsonc +14 -5
  375. package/guides/connecting-agents.md +157 -0
  376. package/guides/how-it-works.md +156 -0
  377. package/guides/safety-and-trust.md +153 -0
  378. package/package.json +27 -46
  379. package/templates/node/.env.example +3 -0
  380. package/templates/node/README.md +22 -2
  381. package/templates/node/docker-compose.yml +1 -0
  382. package/templates/node/package.json +3 -2
  383. package/templates/node/src/connecta.config.ts +8 -0
  384. package/dist/catalog-drift.d.ts +0 -138
  385. package/dist/catalog-drift.js +0 -578
  386. package/dist/providers/_shared/request-token.js +0 -83
  387. package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
  388. package/dist/providers/_shared/rest/tools.d.ts +0 -141
  389. package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
  390. package/dist/providers/basecamp/index.d.ts +0 -45
  391. package/dist/providers/basecamp/index.js +0 -997
  392. package/dist/providers/basecamp/skill.generated.js +0 -9
  393. package/dist/providers/cloudflare/rest.d.ts +0 -37
  394. package/dist/providers/cloudflare/rest.js +0 -1097
  395. package/dist/providers/github/hosted.js +0 -218
  396. package/dist/providers/github/rest.js +0 -321
  397. package/dist/providers/infisical/index.d.ts +0 -7
  398. package/dist/providers/infisical/index.js +0 -713
  399. package/dist/providers/infisical/skill.generated.js +0 -8
  400. package/dist/providers/intercom/index.d.ts +0 -24
  401. package/dist/providers/intercom/index.js +0 -121
  402. package/dist/providers/intercom/skill.generated.js +0 -8
  403. package/dist/providers/linear/index.d.ts +0 -55
  404. package/dist/providers/linear/index.js +0 -537
  405. package/dist/providers/linear/skill.generated.js +0 -8
  406. package/dist/providers/mixpanel/index.d.ts +0 -59
  407. package/dist/providers/mixpanel/index.js +0 -472
  408. package/dist/providers/mixpanel/operations.js +0 -20
  409. package/dist/providers/mixpanel/rest.js +0 -603
  410. package/dist/providers/mixpanel/skill.generated.js +0 -10
  411. package/dist/providers/mixpanel/value-safety.js +0 -16
  412. package/dist/providers/notion/index.d.ts +0 -67
  413. package/dist/providers/notion/index.js +0 -368
  414. package/dist/providers/notion/openapi.generated.js +0 -75
  415. package/dist/providers/notion/rest.d.ts +0 -24
  416. package/dist/providers/notion/rest.js +0 -1226
  417. package/dist/providers/notion/skill.generated.js +0 -11
  418. package/dist/providers/notion/value-safety.js +0 -56
  419. package/dist/providers/revenuecat/index.d.ts +0 -51
  420. package/dist/providers/revenuecat/index.js +0 -697
  421. package/dist/providers/revenuecat/skill.generated.js +0 -11
  422. package/dist/providers/stripe/rest.d.ts +0 -14
  423. package/dist/providers/stripe/rest.js +0 -402
  424. package/dist/providers/vercel/rest.d.ts +0 -20
  425. package/dist/providers/vercel/rest.js +0 -1020
  426. package/dist/providers/wispr-flow/index.d.ts +0 -14
  427. package/dist/providers/wispr-flow/index.js +0 -117
  428. package/dist/providers/wispr-flow/skill.generated.js +0 -8
  429. /package/dist/providers/github/{key.js → lib/key.js} +0 -0
@@ -50,12 +50,40 @@ deployment shapes construct it outside the request handler.
50
50
  An OAuth connector, `remoteMcp()` or `api()` with a static grant, owns a
51
51
  runtime-local refresh completion gate in `src/auth/downstream-oauth.ts`.
52
52
  Joined scopes share the outcome while keeping their own request I/O and
53
- cancellation. A shared-storage CAS lease prevents independent isolates from
54
- redeeming the same refresh token concurrently. The holder commits an accepted
55
- rotation to the grant by CAS before releasing waiters, even when the owner
56
- cancels after dispatch. The HTTP exchange uses its own 20-second deadline,
57
- detached from caller cancellation, and the runtime passes its completion and
58
- commit to Workers `waitUntil`. Contenders read the committed tokens or typed
53
+ cancellation. Grant use and the caller's wait belong to the call. Renewal
54
+ preparation is call-owned only until the first durable coordinator write
55
+ (the claim CAS) is submitted. Closing the call or request before
56
+ submission promptly refuses with "This call has ended…": no CAS, no token
57
+ request and no state change. Late read results cannot restart preparation.
58
+ Claim submission transfers renewal synchronously to the storage coordinator,
59
+ before CAS completion or token dispatch. The coordinator retains its own
60
+ 35-second preparation deadline and then a 20-second deadline for the token
61
+ request and response body, followed by the fenced rotation commit. Neither
62
+ the call nor request signal controls this work after submission. A winning
63
+ claim proceeds to token exchange and commit; a losing claim follows existing
64
+ contention/adoption rules. The runtime passes completion to Workers
65
+ `waitUntil`. Cancellation cannot abandon a fenced claim or strand a spent
66
+ refresh token, and introduces no rollback or re-consent exception. A shared
67
+ storage CAS lease prevents independent isolates from redeeming the same
68
+ refresh token concurrently. The holder commits an accepted rotation by CAS
69
+ before releasing waiters, even after caller closure. Closing a call or request
70
+ still promptly ends that caller's wait.
71
+ For a shared remote MCP connection, the challenged response identifies the
72
+ call that owns renewal preparation. The connection and OAuth provider remain
73
+ request-owned; their longer lifetime cannot keep pre-claim renewal alive
74
+ after its initiating call closes.
75
+ Authorization-code exchange transfers no refresh ownership: its auxiliary
76
+ token POST and pending wait abort on explicit request revocation even when
77
+ the operation has only a `requestScope` and no external signal. A response
78
+ that arrives afterward cannot persist tokens. The callback route keeps its
79
+ existing uninterruptible ownership and does not revoke its own scope during
80
+ exchange; explicit operation revocation is a separate boundary. An aborted
81
+ code exchange can be authorized again without stranding a durable refresh
82
+ claim.
83
+ The closed call consumes no renewal result, marks no ledger entry renewed,
84
+ and sends or resends no resource request. This preserves a rotation after its
85
+ refresh token has been spent while keeping call capabilities revoked.
86
+ Contenders read the committed tokens or typed
59
87
  verdict. An unsent claim can be taken over after expiry. Dispatch uses a lease CAS
60
88
  transition and a CAS of the token fingerprint into permanent spent storage
61
89
  before sending. Fingerprint records have no TTL and carry outstanding,
@@ -79,7 +107,7 @@ request that created them. INV-7 requires this lifetime: a client retained
79
107
  across requests on Workers is a cross-request capability leak, and a promise awaited after the response is
80
108
  work the runtime may already have torn down. Deferred work has one sanctioned
81
109
  channel, `ctx.waitUntil`, threaded through `fetch(request, env, ctx)` — activity
82
- writes and dispatched OAuth grant commits use it. Catalog listing and SQL cache
110
+ writes and OAuth renewals from claim submission use it. Catalog listing and SQL cache
83
111
  publication finish in the requesting scope. A fresh
84
112
  `McpServer` per request is what makes the deployment stateless: no sessions, no
85
113
  server push, no stream resumability, scope resolved rather than remembered.
@@ -119,7 +147,7 @@ reordering reads like a harmless refactor.
119
147
  An admitted non-preflight `/mcp` request then takes five steps in
120
148
  `src/routes/mcp.ts`:
121
149
 
122
- 1. **Admit.** No request body is read before auth on any endpoint. A request
150
+ 1. **Admit.** The fetch-native MCP route reads no body before auth on any endpoint. A request
123
151
  whose `Mcp-Method` header names `subscriptions/listen` takes no permit
124
152
  before auth. After auth and endpoint admission connecta reads at most
125
153
  64 KiB (`MAX_LISTEN_CLASSIFICATION_BYTES`) of it: past that it answers
@@ -128,10 +156,44 @@ An admitted non-preflight `/mcp` request then takes five steps in
128
156
  `-32601`; anything else takes a permit then, so the header alone bypasses
129
157
  nothing. Auth and classification share the configured request lifetime.
130
158
  Other requests take one permit from the deployment-wide pool, taken before
131
- auth so an unauthenticated flood costs a permit rather than a Clerk lookup,
159
+ auth so an unauthenticated flood costs a request permit before an auth lookup,
132
160
  and held until the response _body_ completes, the caller leaves, or its
133
161
  configured lifetime ends, not until the handler returns.
134
- 2. **Authorize.** `/agent/<pool>` consults only the built-in `cta_` adapter
162
+ 2. **Authorize.** Every adapter invocation passes the independent fixed
163
+ 16-active-authorization bound in `src/auth-admission.ts`, including header-named
164
+ listens, SDK-owned auth, agent-token storage, and operator/connection routes
165
+ outside the MCP pool. Shared state is counters and bounded plain charge
166
+ records, never request I/O; there is no queue or shared promise. Saturation
167
+ returns retryable 503 `authentication_busy` with `Retry-After: 1` and a
168
+ checked payload-free log. Each request releases exactly once after the
169
+ adapter and its cancellation cleanup settle; abort alone does not release
170
+ capacity. At its hard deadline plus one second of grace, unfinished work
171
+ moves to a separate allowance of 16 abandoned operations, or stays active
172
+ while that allowance is full. A full allowance refuses all new work, logged
173
+ as `authentication_abandoned_saturated`; only a charge's end decrements it
174
+ and promotes an overdue active charge. A charge ends exactly once, through a
175
+ released state in its plain record: on settlement, or when the collector
176
+ frees its token. That token is an object held only by the closures
177
+ attached to the adapter promise and the registered native operations (plus
178
+ the deadline timer and the request's lifetime entry, which refers to the
179
+ lifetime through a `WeakRef`, because workerd keeps a `Request` and its
180
+ WeakMap entries alive past its invocation). A module-level
181
+ `FinalizationRegistry` holds the record, never the token, so a freed token
182
+ proves nothing can settle the charge; the next admission logs
183
+ `authentication_unreachable`. Without `FinalizationRegistry` (workerd
184
+ without `enable_weak_ref` before 2025-05-05) only settlement releases.
185
+ Health exposes both counters and `unreachableRelease`. Access
186
+ and agent verification race five-second deadlines while registering their
187
+ uncancellable native work with the invocation's charge. The request's
188
+ `ctx.waitUntil` keeps that same settlement continuation runnable on Worker
189
+ disconnects. No other request touches its promise, signal or I/O. Clerk
190
+ uses a client per authorization, with its signal and a ten-second deadline
191
+ across verification, JWKS, handshakes, session refresh, domain and gate
192
+ lookups. The SDK transport buffers at most 1 MiB, closes every upstream body,
193
+ and makes no JWKS retries; no live response escapes to an SDK clone. No current adapter exposes a cheap,
194
+ fully authenticated cache hit, so the entire adapter invocation counts.
195
+ [Authentication capacity](./auth.md#authentication-capacity-and-ingress-limiting)
196
+ lists exemptions and ingress requirements. `/agent/<pool>` consults only the built-in `cta_` adapter
135
197
  (`authorizeAgent`), never with the runtime context that carries
136
198
  `ctx.access`. Human endpoints refuse a `cta_` token before recognition or
137
199
  storage, and never ask the `cta_` adapter. A provider's
@@ -284,6 +346,34 @@ permit has its own deadline even if the recipient never resumes. Lease identitie
284
346
  make an old late release harmless to a newer permit. Set the bound above the
285
347
  longest admitted request the deployment expects to serve.
286
348
 
349
+ `admission.requests.maxBodyBytes` bounds each MCP request body at `/mcp`,
350
+ `/mcp/<pool>` and `/agent/<pool>`. It defaults to 4 MiB (4,194,304 bytes).
351
+ This restricts bodies that previously reached the host's larger platform cap;
352
+ deployments needing larger MCP envelopes must configure the bound explicitly.
353
+ A configured value must be a positive safe whole number; invalid values fail
354
+ construction with their configuration path (INV-11). After authentication and
355
+ endpoint admission, core counts streamed bytes before any SDK or native tool
356
+ body reader. A body exactly at the cap proceeds through normal protocol
357
+ validation. Past the cap, core stops reading, cancels both stream branches and
358
+ returns HTTP 413 with JSON-RPC code `-33006`, `"MCP request body too large."`.
359
+ The response retains the request's existing admission lifetime and releases
360
+ the permit when delivery ends. Caller cancellation and the existing request
361
+ deadline also cancel stalled reads. `describeConfig().limits.requests.maxBodyBytes`
362
+ reports the resolved value and its `default` or `config` source.
363
+
364
+ The pre-dispatch `subscriptions/listen` classification keeps its 64 KiB ceiling;
365
+ a smaller deployment body cap wins. Authentication failures and private endpoint
366
+ refusals still read no body. This envelope bound does not change program size,
367
+ tool argument validation, write accounting or result limits.
368
+
369
+ The Node `listen()` adapter separately buffers requests before calling the core,
370
+ with its own `maxBodyBytes` option. Its default is also 4 MiB. It stops at the
371
+ first chunk crossing that bound and returns a plain HTTP 413 before auth;
372
+ core's authenticated refusal uses the JSON-RPC envelope above. The smaller bound
373
+ wins. To admit larger bodies on Node, raise both options deliberately. Workers
374
+ and hosts calling `connecta.fetch` apply core's bound after authentication,
375
+ subject to any smaller platform or proxy limit.
376
+
287
377
  Call admission (`src/call-admission.ts`) answers what the envelope cannot see —
288
378
  a connector's optional policy over its own `Connector.callTool` attempts,
289
379
  partitioned by an optional `partitionKey` and bounded by concurrency, a
@@ -364,6 +454,32 @@ the same hard deadline and is aborted in its own context.
364
454
  Cloudflare documents the flag in its
365
455
  [Request API](https://developers.cloudflare.com/workers/runtime-apis/request/).
366
456
 
457
+ ### Authentication release by collection on workerd
458
+
459
+ Measured on 2026-10-20 with workerd 1.20260722.1 (Miniflare 4.20260722.0),
460
+ compatibility date `2025-01-01`:
461
+
462
+ - `FinalizationRegistry` and `WeakRef` are undefined without
463
+ `enable_weak_ref`; the flag's default-on date is 2025-05-05.
464
+ - With `enable_weak_ref` and `enable_request_signal`, an object reachable only
465
+ through a fetch continuation is finalized once its invocation ends and
466
+ workerd cancels the fetch; while the invocation runs it is not. Pending
467
+ timers and listeners on the request's signal do not keep it.
468
+ - Strong values in a WeakMap keyed by a `Request` (even one the handler
469
+ created), properties set on a `Request`, and WeakMap values keyed by its
470
+ `Headers`, `ctx` or `env` stay reachable after the invocation ends. The
471
+ lifetime registry in `src/auth-admission.ts` therefore stores `WeakRef`s.
472
+ - No `gc()` is exposed. Allocating large arrays (32 × 1 MiB per request)
473
+ triggers collections, but the most recent unreachable object often survives
474
+ them; V8 in this build scans the native stack conservatively. The inspector's
475
+ precise `HeapProfiler.collectGarbage` freed it.
476
+ - In the vitest-pool-workers harness, an isolate that had already collected
477
+ one scenario's cancelled work sometimes kept a later scenario's cancelled
478
+ work reachable through 400 rounds of pressure; standalone Miniflare never
479
+ did. `test/auth-admission-binding.test.ts` gives each collection scenario a
480
+ fresh isolate and requires two of four sentinels collected before asserting
481
+ that live charges stayed counted.
482
+
367
483
  A second production run on the same date tested the request bound at 2,000 ms.
368
484
  All twelve raw-stream and MCP cases passed with both flag configurations. Live
369
485
  MCP requests still rejected a competing request with 503 before the deadline;
@@ -555,14 +671,62 @@ that refuses deletion does not block another Worker's deletion. It runs no real
555
671
  hardening and the joint convergence are verified offline only and have not run
556
672
  against Cloudflare.
557
673
 
674
+ ### Alchemy Worker deployment
675
+
676
+ Two throwaway deployments on 2026-10-20 verified the Worker example's
677
+ Alchemy path with alchemy 2.0.0-beta.83, effect 4.0.5, and Alchemy local
678
+ state, then deleted every resource and confirmed by re-listing. Alchemy
679
+ uploads the full Worker with `PUT /workers/scripts/<name>`, the endpoint an
680
+ earlier full deploy had been suspected of losing `ctx.access` through.
681
+
682
+ The first deployment ran a near copy of the stack with a temporary diagnostic
683
+ route. With an Access service token in a non-identity policy, the Worker saw
684
+ `ctx.access` carrying the Worker-level application's AUD and no identity, as
685
+ expected for a service token. D1, the Worker Loader, R2, and secret bindings
686
+ worked; `/agent/*` reached connecta through the bypass; `/ui/api/config`
687
+ returned 403 `Cloudflare Access human identity required`; and
688
+ `connecta doctor` passed with a `cta_` token issued inside the Worker. A code
689
+ update through `alchemy deploy` kept the script tag, `ctx.access`, and D1 rows.
690
+ Three consecutive probe rounds agreed.
691
+
692
+ The second deployment ran the shipped `alchemy.run.ts` from the packed package
693
+ in a separate project on Node 26, changing only resource names and the Access policy
694
+ (a service token). The edge checks in the example's "Verify after deploying"
695
+ held for three or more rounds after each deploy. `alchemy destroy` deleted the
696
+ Worker and both Access applications and orphaned the retained D1 database; the
697
+ next deploy adopted it by name with its rows. After deleting local state, a
698
+ deploy reused the Worker with the same script tag, and `applicationId` took
699
+ both Access applications over with unchanged AUDs and no duplicates. Removing
700
+ `applicationId` afterwards planned an in-place update, and a further deploy
701
+ planned no changes. The account API token needed only Workers Scripts Write,
702
+ D1 Write, Access: Apps and Policies Write, and Access: Organizations, Identity
703
+ Providers, and Groups Read.
704
+
705
+ Four findings shape the operator rules. Alchemy local state under
706
+ `.alchemy/state/` records secret binding values in plaintext. Alchemy sends no
707
+ `keep_bindings`, so each deploy deletes secrets set outside the stack.
708
+ `alchemy dev` does not provide `ctx.access` to a plain fetch Worker, so local
709
+ development stays on `wrangler dev` and its `access.dev` identity. D1 needs
710
+ `RemovalPolicy.retain()` so `alchemy destroy` cannot delete a deployment's
711
+ state. Not verified: `Cloudflare.state()` remote state, custom domains, and
712
+ taking over a Worker first deployed with Wrangler.
713
+
714
+ npm strips `.gitignore` from the package, so the copied Worker example's
715
+ ignore rules ship as `examples/worker/gitignore`, renamed by the README's copy
716
+ step. `test/deployment-shapes.node.test.ts` checks a copy of the source tree,
717
+ and `scripts/check-package.mjs` extracts the packed example with the README's
718
+ `tar` command; both confirm git leaves `.alchemy/`, `.env*`, `.dev.vars*`,
719
+ `.wrangler/`, and `node_modules/` untracked.
720
+
558
721
  ## Storage, credentials, and connectors
559
722
 
560
723
  A deployment has one store. On Workers it is one D1 database
561
724
  (`d1Storage(env.CONNECTA_DB)` from `@zackbart/connecta/d1`); on Node it is one
562
725
  SQLite file (`sqliteStorage(path)` from `@zackbart/connecta/sqlite`, over the
563
726
  built-in `node:sqlite`). Both are the same SQL key-value store
564
- (`src/storage/sql.ts`) over a three-method driver, so every statement runs
565
- unchanged on each, and the same module carries the activity table beside it.
727
+ (`src/storage/sql-native.ts`) over a native Effect SQL execution service, so
728
+ every statement runs unchanged on each, and the same module carries the
729
+ activity table beside it. `src/storage/sql.ts` owns the Promise edges.
566
730
  Each store creates its tables on first use. `memoryStorage()` is the default
567
731
  and the test double. Workers KV and the 0.28 JSON file store are gone: KV is
568
732
  eventually consistent and cannot compare-and-set, and the file store rewrote
@@ -596,7 +760,7 @@ is one statement, which SQLite executes atomically and D1 serializes on its
596
760
  primary. Everything that must claim a key exactly once relies on it with no
597
761
  read-then-write fallback: agent-token slot swaps and
598
762
  adoption, OAuth handoff ownership, single-use connect links, every downstream
599
- OAuth grant write and consent claim, and the result stash.
763
+ OAuth grant write and consent claim, the result stash, and initialization of the cursor signing key.
600
764
 
601
765
  Every key is built in `src/storage/keys.ts`, which lists each family with its
602
766
  scope, version, codec, and TTL policy; `test/storage-keys.node.test.ts` fails when
@@ -782,17 +946,186 @@ auth headers, raw confidential OAuth client IDs and secrets, and outbound
782
946
  bearer tokens join the set, including tokens rotated during a call. Token and
783
947
  revocation requests register client credentials before dispatch, decoding OAuth
784
948
  Basic form components so a raw echo is covered too. Public client IDs remain
785
- visible. Before any diagnostic is truncated or returned, the
786
- agent boundary replaces these values and their auth prefixes, mixed JSON
787
- escapes, URL encodings, and base64/base64url forms with `[redacted]`. Final
788
- transports register sensitive headers and query values after assembling the
789
- request; custom API handlers use `ctx.fetch`. Values shorter than eight
949
+ visible. Redaction replaces these values and their auth prefixes, mixed JSON
950
+ escapes, URL encodings, and base64/base64url forms with `[redacted]`.
951
+
952
+ Every redaction is one pass over a text projection of the whole value: string
953
+ and number leaves in serialization order, nested JSON strings parsed into their
954
+ own leaves, and errors' own fields (non-enumerable diagnostics and causes
955
+ included). The joined projection is matched once, raw and through each JSON
956
+ decoding layer, so a form split across adjacent leaves is found; each match is
957
+ replaced only in the leaves it touches, so public runs, links, annotations and
958
+ container shapes stay. The walk reads every field once, from its descriptor, and
959
+ returns what it checked. A caller's container stands only as verified plain
960
+ data: prototype `Object.prototype`, `Array.prototype` or `null`, no `toJSON` on
961
+ its chain, no symbol-keyed field, every field a data property it read (an
962
+ array's holes read through its prototypes), and nothing in it changed. Anything
963
+ else is a copy of writable, configurable data properties, so a frozen input
964
+ never throws, and every value returned is JSON data, a fresh built-in
965
+ `Uint8Array`, or an error copy: an error always, as its own class, with its
966
+ stack and every accessor its class defines read once into checked fields, its
967
+ `toJSON` shadowed unread and no function in a field; an object of another class
968
+ as plain data; an array that is not plain as a plain array of its indices; a
969
+ `String`, `Number` or `Boolean` wrapper as its primitive; a `toJSON` result,
970
+ called once and checked; a function or symbol as the placeholder; a typed array,
971
+ `DataView` or `ArrayBuffer` as a fresh `Uint8Array` of its bytes, which are
972
+ checked as bytes and, with the elements it held, as the numbers JSON shows for
973
+ them. The type table is in [auth.md](./auth.md). An own accessor is
974
+ never invoked: an object's copy leaves it out, an array index holds the
975
+ placeholder. A change copies the containers on its path on first write, so a
976
+ caller's value is never mutated. An array's non-index fields are never read:
977
+ JSON, which every exit and executor marshals with, never serializes them.
978
+ Facts kept beside a value by identity (failure facts, classifications, statuses,
979
+ Google's reasons and outcomes) register with `redaction-identity.ts` and follow
980
+ its copies, arrays' included.
981
+ One checker, `checked()`, matches every output-bound string under the whole
982
+ lattice: the projection's leaves as one lane, and alone each object key,
983
+ scalar, cursor claim, encoding Connecta produces and structural field;
984
+ provider lanes and bounded reads call it too. Its matching steps run nowhere
985
+ else. Only fixed
986
+ words are kept whatever was sent: envelope keys, MCP and JSON-RPC member names
987
+ and tag values, booleans under outcome names, and on an error its
988
+ classification code and a name its class is registered with in
989
+ `redaction-identity.ts` (the built-in errors, `DOMException`, and Connecta's
990
+ and the pinned MCP SDK's own classes, each with literals beside the class); a
991
+ number or string under any name is matched like any other, and any other error
992
+ name is a projection leaf like any field, joined with its neighbours, and
993
+ withheld whole as `[redacted]` when the lane changes any part of it, keeping
994
+ the copy's class. An accessor an error's chain defines is held on its copy under its
995
+ checked name. Exact relay states and connect links
996
+ Connecta mints and authenticates are kept whole: they hold only sealed state or
997
+ Connecta's own facts. Serialized JSON whose keys, syntax and leaves together
998
+ hold a run of a form that no single leaf holds is withheld whole.
999
+
1000
+ The guarantee: **no caller-visible text holds a registered form whole, nor any
1001
+ run of K = 8 consecutive UTF-16 units of a registered form of a value at least K
1002
+ long, in the raw text or any JSON-decoded layer; text cut where the cut can end
1003
+ inside a form loses that unfinished tail, however short.** Registered forms are
1004
+ the raw value, its URL/form encodings (either percent hex case), base64 and
1005
+ base64url, and the `Bearer `/`token ` framings. The framing words are public, so
1006
+ no indexed run starts inside them; a run reaching K units of the value is
1007
+ indexed from the value's own forms, and an echo carrying the framing is
1008
+ replaced with it. A private value of one to three units is matched only as a
1009
+ whole field value (a leaf, key, number, boolean or decoded view equal to one of
1010
+ its forms); one of four to seven units also as a substring of payload text,
1011
+ numbers, keys and decoded views; one of K or more also by every K-unit run.
1012
+ Pieces shorter than K are found only where they are adjacent: in the joined
1013
+ projection or one provider lane (one Slides shape's text runs, one Docs document). Non-adjacent pieces shorter than K, such as
1014
+ two seven-unit halves of a value in two Slides shapes, survive: they are
1015
+ indistinguishable from ordinary text without redacting ordinary words. Runs of
1016
+ K-1 units survive away from a cut. Values derived by an unregistered encoding
1017
+ (a date from a timestamp, say) are outside the guarantee, as is a later read of
1018
+ a Proxy, which only in-process code can build.
1019
+
1020
+ Payload passes run where downstream or guest data enters: the connector's
1021
+ result and errors, the invocation's raw reply, executor outcomes and logs,
1022
+ emits, and resource and skill reads. The agent boundary (`redactAgentOutput`,
1023
+ every meta-tool exit and the MCP route) repeats every check except short
1024
+ private values, which would rewrite Connecta's own ids, counts and addresses;
1025
+ payload passes already removed them. Identifiers sent beside a credential — a
1026
+ confidential client's ID, a Basic username beside a password of at least eight
1027
+ characters, and credential-slot fields named as identifiers (`…Id`, `user`,
1028
+ `username`, `login`, `email`, `public…`) — are matched whole only: consent URLs
1029
+ and vendor metadata carry their parts by design. Continuation cursors sent as
1030
+ query parameters (`pageToken`, `next_token`, `continuationToken`, `syncToken`)
1031
+ are public result state and are not registered. Header values a connector
1032
+ adds to token requests are registered under any name, except values equal to
1033
+ Connecta's own public ones (`Accept: application/json` and maintained
1034
+ providers' media types, in `PUBLIC_TOKEN_REQUEST_HEADERS`).
1035
+
1036
+ K = 8 equals the credential floor, so exactly the short private values are
1037
+ whole-only. It keeps ordinary words, dates and counts out of the index: a public
1038
+ run must repeat eight consecutive units of one form to match, so high-entropy
1039
+ credentials and private values produce no measured false positives (a 200 KB
1040
+ corpus of documents, logs, JSON and URLs is unchanged). Values that embed
1041
+ ordinary text do suppress it wherever it recurs within the same request: a
1042
+ password holding the word `resource`, a token holding a vendor's name, a
1043
+ credential that is a URL, or a PEM key's armor. Base64 forms reach K at six
1044
+ bytes of the value. The index stores two independent 32-bit polynomial hashes
1045
+ per run (at most 524,288 distinct runs, a table at most half full), so a false
1046
+ match is vanishingly rare and only redacts more; index and scan are linear in
1047
+ the registered forms and the scanned text, whatever the number of values. A
1048
+ 64 Ki-bit bitmap of the runs' first hashes rejects nearly every text position
1049
+ in one load, so only a candidate computes its second hash. A pass over a large
1050
+ body costs about what the per-leaf matcher it replaced did
1051
+ (`scripts/bench-sent-secrets.mjs` compares builds); declared binary content
1052
+ costs more, since it is decoded and checked as bytes as well as its base64 text.
1053
+
1054
+ Unchecked content is withheld rather than weakening the guarantee: past the
1055
+ index's capacity, or under a private value too large to register, every leaf of
1056
+ K units or more and every run of adjacent shorter leaves reaching K; a cycle, an
1057
+ accessor, a container past depth 512, and a container past the node budget,
1058
+ withheld whole and never serialized. Leaves too large to match joined within the call's work budget are
1059
+ matched alone, in order, withholding one that does not fit, and every seam
1060
+ between kept leaves is matched 512 units deep on each side. Text that still
1061
+ holds escapes after eight decoding layers withholds the leaves holding them.
1062
+
1063
+ Declared binary content (Drive `format: "base64"` content, MCP image/audio
1064
+ `data`, resource `blob`, and a REST body's `base64` beside its `contentType`)
1065
+ is decoded and checked as bytes, as UTF-8 when a registered form is not ASCII,
1066
+ as its base64 text, and through any base64 the bytes carry, so an echo at any
1067
+ byte alignment is found. Decoded views take payload text's short-value rules. A
1068
+ match withholds the whole field as the base64 of `[redacted]`, never partially
1069
+ scrubbed bytes; content that is not valid base64 as it stands still gets every
1070
+ text view. Base64 or base64url in any string, key or declared field is decoded
1071
+ in each of its text views: raw, one layer of JSON escapes decoded, percent
1072
+ escapes decoded, and the form decoding (a `+` as a space, generated whenever
1073
+ a `+` appears). A view that
1074
+ is wholly base64 is decoded from six units; a run inside one from six units
1075
+ when payload rules apply and a four-to-seven-unit private value is registered,
1076
+ otherwise from eleven. A run of L units decodes to floor(3L/4) bytes, so a piece of
1077
+ p bytes at alignment a needs L >= ceil(4(a + p)/3), least at a = 0: the smallest
1078
+ piece matched is a short value's four bytes (6) or a K-unit run's eight (11).
1079
+ Decoded content is searched for further base64 three encodings deep. The
1080
+ decoded views are matched together as one lane, each run decoded once per call
1081
+ however many leaves repeat it, and a match only decoding reveals withholds the
1082
+ leaf as `[redacted]`. Every decode is charged to the call's work budget, and a
1083
+ leaf it cannot check is withheld. Two text decodes chained, base64 glued to
1084
+ preceding alphabet units with no separator, HTML entities, compression and
1085
+ custom alphabets are outside the guarantee (`auth.md`, INV-5 output redaction
1086
+ limits). Content found clean at least 4,096 units long is remembered for the
1087
+ request until its set changes, so later passes do not decode it again.
1088
+
1089
+ Every base64 or base64url encoding Connecta returns goes through
1090
+ `encodeForAgent`: Drive and Gmail downloads, REST bodies, and every page
1091
+ cursor. It checks the content before encoding and encodes `[redacted]` instead
1092
+ on a match. Cursors are built from already-redacted values, not sealed: a
1093
+ provider has no deployment key, and a sealed vendor token would still be the
1094
+ vendor's text. A vendor page token is redacted where it is placed, so one that
1095
+ echoes a sent value fails on the next page instead of carrying it; a revision or
1096
+ id the cursor only compares becomes its digest when it holds a sent value
1097
+ (Slides states, Forms revisions and anchors), and a vendor page id a cursor
1098
+ names (Slides slides and notes pages) is redacted. Before encoding, every
1099
+ string in a cursor's claims is checked under payload rules, since each
1100
+ downstream value a cursor holds is a string, and its whole text and encoding
1101
+ under the boundary's, so Connecta's own keys and counts never meet short
1102
+ private values. A cursor checked so is remembered as clean; one that holds a
1103
+ match encodes `[redacted]`.
1104
+
1105
+ Text Connecta or a provider cuts short, trims, normalizes or extracts from is
1106
+ redacted whole against the request's set, private arguments included, first
1107
+ (`redactBeforeCut`, and `clampText` in the Workspace products): a cut leaves a
1108
+ remnant shorter than K that no later pass can see, and a normalized number may
1109
+ no longer hold the form at all (Drive size strings, Breeze amounts and dates).
1110
+ Provider text lanes that the projection does not see as adjacent are matched
1111
+ joined (`SentSecrets.parts`): Docs text runs across links and paragraphs, with
1112
+ index previews checked again after whitespace collapses; and Slides raw
1113
+ text runs, which carry style between them. Text read only up to a bound, or that
1114
+ a vendor stopped listing, also loses any suffix that begins a registered form
1115
+ (`redactBoundedRead`). Complete forms and open-tail prefixes are matched against
1116
+ the original text and merged before replacement. Each decoded layer sets aside
1117
+ its own incomplete trailing JSON escape, so a later layer cannot conceal an open
1118
+ tail. Only the uncertain suffix changes; checked earlier rich-text runs retain
1119
+ their links and annotations. Text-offset cursors bind the redacted
1120
+ representation as well as the vendor revision; changed secret sets that change
1121
+ that representation refuse continuation with a restart instruction.
1122
+
1123
+ Final transports register sensitive headers and query values after assembling
1124
+ the request; custom API handlers use `ctx.fetch`. Values shorter than eight
790
1125
  characters do not enter the matcher, including Basic usernames, passwords
791
1126
  and explicitly secret fields such as `client_secret`. Configured short
792
1127
  credentials produce one payload-free warning per connector; use secrets of
793
- at least eight characters. Encoded and JSON-escaped echoes of registered
794
- credentials follow the same redaction rules. Connecta's own messages
795
- never quote a credential.
1128
+ at least eight characters. Connecta's own messages never quote a credential.
796
1129
  Core uncertainty and retry envelopes share one schema-filtered argument snapshot,
797
1130
  captured before dispatch and byte budgeting. `writeOnly: true` omits values of
798
1131
  every length, including nested properties, array items and prefixes, local
@@ -832,7 +1165,11 @@ including titles, descriptions, schemas and annotations. If a tool name would
832
1165
  change, the complete catalog is refused. Rewriting a name changes dispatch;
833
1166
  dropping only that tool would publish a partial catalog, against INV-8. Later
834
1167
  cached reads need no credential set because the stored facts are already clean.
835
- Echoed sensitive header lines are also withheld. The set is never persisted
1168
+ Echoed sensitive header lines are also withheld. They are read on the text
1169
+ before the echo is replaced and merged into its spans, so a private value that
1170
+ takes part of a header's name, its `:` or a credential prefix (`Bearer `) cannot
1171
+ hide the header: the whole value goes, every cookie pair and any short tail
1172
+ with it (#703). A line that is not a sensitive header keeps its text. The set is never persisted
836
1173
  or logged. Anything else (a transport, parser, stream, validator, or
837
1174
  runtime error) reaches it in connecta's words: step, origin, HTTP status, and
838
1175
  class, classified as the original would have been ([auth](./auth.md#what-a-servers-errors-may-say)).
@@ -855,7 +1192,10 @@ operator page may show catalog metadata the operator loaded (descriptions,
855
1192
  schemas), but withholds a tool name outside the grammar as it does in records.
856
1193
  `test/operator-record-sources.node.test.ts` is a secondary lint over every other
857
1194
  log call in `src/`. Fix the sink, not the source: a filter at each source missed
858
- the next one.
1195
+ the next one. Recorded HTTP status facts survive the bare write-401 fallback,
1196
+ Google's uncertain-write wording, and oversized-response mapping; see
1197
+ [Read failure records](./operating.md#read-failure-records) for their operator
1198
+ meaning. These facts do not change agent error fields or write outcomes.
859
1199
 
860
1200
  ## Configuration
861
1201
 
@@ -888,10 +1228,9 @@ factory) walk their own options the same way, against shapes the compiler
888
1228
  checks against their option types (`optionsOf<T>()`). Provider definitions
889
1229
  declare their closed `options` shape; `defineProvider()` validates it by the
890
1230
  same descriptor walk before `create` reads a value and stamps the definition
891
- name onto `describe().source.provider`. `classify` is accepted by the remote
892
- shape and validated by the shared reviewed-classification validator. Custom
893
- connectors remain opaque, including their `classification` field, which the
894
- registry validates. Checks
1231
+ name onto `describe().source.provider`. Hosted MCP uses generic runtime discovery;
1232
+ `classify` is rejected at construction in favor of `ConnectaConfig.classification`.
1233
+ Custom connectors remain opaque and classify from annotations. Checks
895
1234
  that need the connector set — pool members, exact classification overrides, a
896
1235
  connector's own `maxResultBytes` — throw from `src/index.ts` and the registry
897
1236
  under the same policy. `storage` stays opaque, but its check requires `list`
@@ -917,64 +1256,91 @@ switched by the environment, and keep their entries under 30 lines.
917
1256
 
918
1257
  ### Providers and reviewed classification
919
1258
 
920
- Infisical separates explicit value reads from identifier results. `get_secret`
921
- and `list_secrets` with `includeValues: true` return secret values and comments.
922
- All other results, including imported secrets and pending approvals, allow only
923
- checked IDs, keys, environment and tag slugs, paths, timestamps, positive versions
924
- and enum fields. They omit project slugs derived from free-text names, comments,
925
- descriptions, free-form names, reminder notes and arbitrary nested objects.
926
- `metadataOmitted: true` reports withheld or unavailable metadata.
927
- This assumes an honest server and protects against careless human entry in free
928
- text. A malicious server encoding values into valid identifiers is out of scope.
929
- Known, accepted residual risk: environment slugs are returned as required
930
- routing identifiers. Infisical's UI derives them from environment names by
931
- default, so secrets entered in those names can appear in returned slugs.
932
- Operators must not put secrets in environment names or slugs. Secrets placed in
933
- identifiers or key names are out of scope.
934
- Write input schemas mark secret values, secret comments and folder descriptions
935
- as `writeOnly`, so recovery argument echoes omit them without changing dispatch.
936
- Submitted values never join the request's sent-credential set, preserving later
937
- program arguments. Client credentials and tokens still join that set on every
938
- call. The [provider guide](https://github.com/zackbart/connecta/blob/main/src/providers/infisical/SKILL.md) owns routing and
939
- value-read conventions.
940
-
941
1259
  A maintained provider is one `defineProvider()` call (`src/provider.ts`): a
942
- name, title, kind (`"mcp"`, `"api"`, `"composed"`, or `"dual"`), a maintained skill, an
943
- optional reviewed classification, a closed `options` shape declared with
944
- `optionsOf<O>()`, and a synchronous `create`. The root exports the option-shape
945
- combinators and `PROVIDER_COMMON` so provider authors use the same validation
946
- path. The factory refuses undeclared keys and accessors before reading any
947
- option, copies plain data, preserves behaviour objects, then validates common
948
- values (purpose, title, instructions, `authScope`) before `create` runs, and renders the guide: heading, the
949
- connection context `create` supplies, the maintained text, then deployment
950
- instructions, which append and never replace. `src/provider.ts` imports neither
951
- transport, so an `api()` provider gains no MCP client or Effect graph from it
952
- (`test/purity.node.test.ts`). The factory carries its `definition`, which build and
953
- check tools read instead of keeping provider lists. Providers
954
- live in their own folders. Hosted implementations, including the
955
- MCP branches of Notion, Vercel, and Cloudflare, use reviewed presets over
956
- `remoteMcp({ classify })`. The migrated API-only factories retain an internal
957
- adapter to the same definition and description-stamping path. GitHub composes hosted tools with a scope-enforced REST complement.
958
- A `"dual"` provider selects one implementation at construction, usually through `auth`
959
- ([decision 0005](https://github.com/zackbart/connecta/blob/main/decisions/0005-auth-selects-implementation.md)):
960
- `variants(["auth", "type"], …)` closes each case's options, and
961
- `byAuth()` in `src/providers/_shared/rest/dispatch.ts` sends OAuth to
962
- `hostedOAuth()` (`remoteMcp()`, OAuth only, the reviewed classification) and a
963
- key to Connecta's REST connector. Stripe, Notion, Cloudflare, and Vercel require `auth`. Mixpanel is also dual,
964
- with explicit `via: "mcp" | "api"` and MCP as the unchanged default: service accounts
965
- reach both implementations, so auth cannot distinguish them. API mode requires
966
- operator-managed service-account credentials and never falls back to MCP.
967
- Mixpanel uses the same REST transport, operation matching and value-safety engine
968
- for a narrow reviewed subset. Its official contracts are YAML and app identity
969
- has no published response schema, so its guide and drift record carry explicit
970
- manual review evidence instead of claiming a machine-checkable JSON pin.
1260
+ name, title, `kind: "api"`, maintained skill, closed `options` shape and a
1261
+ synchronous `create`. Common values are checked before construction and the
1262
+ factory stamps its provider name onto `describe().source.provider`. API
1263
+ implementations and their contract evidence remain vendored. Hosted MCP servers
1264
+ use generic `remoteMcp()` entries; provider reviews and hosted defaults are
1265
+ removed. Legacy hosted selectors fail at construction with the generic migration
1266
+ path. GitHub retains its scope-enforced App identity and exposes a bounded repository-read and release-write hatch plus local scope discovery, scoped data search and guarded merge.
1267
+
1268
+ [Decision 0011](https://github.com/zackbart/connecta/blob/main/decisions/0011-provider-standard.md)
1269
+ records the provider standard now implemented by all 15 maintained providers. It fixes
1270
+ one folder layout per kind and fixed evidence names. `drift.json` gets a
1271
+ generated block and the guide a generated Conventions section, and every
1272
+ `api()` surface gets one agent-facing shape. Providers completed two migration
1273
+ steps: onto the kit with unchanged output, then onto the standard surface.
1274
+ Both `PENDING.step1` and `PENDING.step2` in `test/provider-standard.ts` are empty;
1275
+ every maintained provider now meets the standard without a migration exemption.
1276
+ `test/provider-standard.node.test.ts` holds every maintained provider folder to the
1277
+ standard, and `test/provider-conventions.test.ts` checks the surface rules on
1278
+ Node. `providers:generate` writes the standard's derived parts and
1279
+ refuses a provider past step 1 that breaks the `drift.json` rules, or one past
1280
+ step 2 that breaks the guide rules: `drift.json` version 2's `generated` block
1281
+ (operations from the kit's `ProviderKitFacts` and evidence digests), and a guide's `## Conventions`, rendered
1282
+ from the definition's declarations into `skill.generated.ts`.
1283
+ The generator requires `fixtures.ts` to export `createAuthored()` with a
1284
+ nonempty offline `staticTools` catalog. Its input/output schemas derive one
1285
+ paging convention, including `get_*`, hatch search and details, excluding
1286
+ tools whose `page.hasMore` is always false. The convention distinguishes
1287
+ top-level `cursor` from `query[page.param]` or `body[page.param]` continuation
1288
+ and tells agents to return `page.nextCursor` before the call that would exceed
1289
+ the per-program call limit, then continue in the next program. The Hatch line
1290
+ reports a nonzero explicit refusal count with singular/plural wording and
1291
+ points to the actual prefixed details tool with method/path lookup; it never
1292
+ embeds the refusal table. Unreviewed operations remain refused.
1293
+ `ProviderContext.usageGuide` selects a guide's `<!-- when: <key> -->` blocks
1294
+ with the definition's `blocks`. `providers:check` reads both record versions,
1295
+ reports a manifest operation no endpoint row records at the provider's level,
1296
+ and lists manual checks last verified more than 180 days ago. Its offline
1297
+ record validator, `scripts/drift-validate.mjs`, also gates `providers:generate`
1298
+ and the standard suite, so a record they accept is one `providers:check` reads.
1299
+
1300
+ Supplemental vendored contracts use a simple `<slug>.json` pin and a sibling
1301
+ `<slug>.document.json` in the provider's `evidence/` directory. An `endpoints`
1302
+ check names the pin with `specification.pin`; both files are hashed in
1303
+ `generated.evidence`. Slugs use lowercase letters, digits and hyphens, start
1304
+ with a letter, and cannot be `openapi` or `value-safety`; paths, dots and
1305
+ reserved primary evidence names are refused. The pin requires https `url`
1306
+ and `documentation`, `vendored: true`, and the exact document `digest`.
1307
+ The checker reads that document offline from this provider's evidence only,
1308
+ checks its digest, and never fetches the pin's URL. Keep dataset semantics
1309
+ that the envelope does not describe in a separate manual check.
1310
+
1311
+ A provider whose public API is split across documents may declare
1312
+ `evidence/openapi.json` as `{ revision, documents: [{ pin, prefix }], options? }`.
1313
+ Each `pin` names a simple supplemental sibling pin and byte-exact document;
1314
+ `prefix` is its literal API path and must match that document's one server.
1315
+ All documents must share an HTTPS origin. A composition has no `url`, `digest`,
1316
+ `latest`, or `vendored` field of its own: its actual source URLs and composite
1317
+ digest are derived from those pins. `providers:spec` namespaces local component
1318
+ references, prefixes paths, omits empty source paths that establish no route,
1319
+ refuses collisions, and compiles the combined
1320
+ contract through the same compiler. Missing operation summaries inherit the
1321
+ path item's resource description with a method-specific action. Both the source
1322
+ pin bytes and normalized composed document are checked for offline freshness.
1323
+ A composition moves only by explicit review of its source pins/documents;
1324
+ `--record` and `--file` cannot replace it. `providers:check` compares the actual
1325
+ public documents independently; it never fetches a synthetic combined URL.
971
1326
 
972
1327
  The generic REST connector (`src/providers/_shared/rest/`) serves
973
1328
  `<vendor>_api_search`, `_api_details`, `_api_read`, and `_api_write` over an
974
1329
  `OperationIndex` that `pnpm run providers:spec` compiles offline from the
975
- provider's pinned `openapi.source.json` into `openapi.generated.ts`.
1330
+ provider's pin (`evidence/openapi.json`; `openapi.source.json` until its step 1)
1331
+ into `openapi.generated.ts`. A `vendored: true` pin compiles the reviewed
1332
+ conversion in `evidence/openapi.document.json` instead of fetching, and the
1333
+ generated header records that document's digest beside the pin's, so the
1334
+ offline freshness check sees a changed conversion.
976
1335
  Each call is matched to an operation and checked against its parameters before
977
- transport; `_api_read` admits GET and the vendor's reviewed read-only POSTs in
1336
+ transport. Resolved path parameters are validated before authentication or dispatch,
1337
+ including pinned enums and numeric bounds. Number path values must decode to canonical finite ASCII decimals without
1338
+ exponents, hexadecimal, a leading plus or whitespace. Integer path values must decode to
1339
+ canonical ASCII decimal safe integers: `0`, positive decimals without leading
1340
+ zeros, or negative decimals without leading zeros. `-0`, leading `+`, whitespace,
1341
+ exponents, decimal points and non-ASCII digits are refused. Negative integers
1342
+ remain valid when the pin has no minimum excluding them. String-valued ids keep
1343
+ their declared string contract; `_api_read` admits GET and the vendor's reviewed read-only POSTs in
978
1344
  its handler, since classification is per tool name. Vendors configure only a
979
1345
  transport, failure mapper, scope, framing, cursor extraction, a reviewed
980
1346
  value-safety table (required; see below), argument-dependent refusals, and an
@@ -986,22 +1352,71 @@ request-header allowlist, text bodies, and index `slashParams` that admit an
986
1352
  encoded `/` in a final key segment (`test/rest-connector.test.ts`).
987
1353
  Large documents opt into generator options that drop restated operation ids
988
1354
  and plain path parameters and cap one operation's details (`opBudget`).
1355
+ `numericBounds: true` preserves documented numeric minimum/maximum constraints
1356
+ in request schemas and enforces them before dispatch, including numeric query
1357
+ strings. Existing indexes without those optional constraints are unchanged.
1358
+ The compiler separately records response property paths from the lowest
1359
+ declared JSON success status (then `2XX`), resolving local references and
1360
+ composition. It includes top-level and list-item properties (root arrays or
1361
+ an envelope's immediate arrays), and shares paths and rows in a lazy `OpenApiData.fields`
1362
+ JSON string. Traversal stops at 1,024 schema visits, 24 reference/composition
1363
+ levels, 256 paths or 200 characters per path; overflow or an unresolved
1364
+ reference omits that operation's whole response-field contract. Explicitly open
1365
+ roots (including root-array items) also leave fields absent. Empty,
1366
+ schemaless and non-JSON responses declare no fields. Request shrinking and
1367
+ endpoint contract digests do not depend on this metadata. Legacy REST tools
1368
+ retain their catalog and discovery behavior until each provider's step 2.
1369
+ Fields remain optional additive metadata, so a previously compiled
1370
+ index without the source bytes remains usable without inventing a response
1371
+ contract. Vercel's step-2 pin review restores response metadata from the reviewed public
1372
+ document; operations without declared response properties still refuse fields. Breeze's pin declares no response
1373
+ properties. GitHub's immutable official OpenAPI pin supplies its complete index and response fields; a reviewed repository allowlist controls dispatch. Natural queries with reviewed matches scope discovery to those matches and named-only operations; explicit paths and unmatched queries retain full-pin refusal discovery.
989
1374
  `check:providers-generated` checks each index header against its source record
990
1375
  offline; `providers:check` reports a pin that differs from the live document.
991
1376
  No maintained provider selects an implementation with `surface` any more. See
992
1377
  the
993
- [provider auth migration](./provider-auth-migration.md). Notion's token
994
- connector keeps its `integration_*` projections and authoring helpers beside
995
- the generic REST tools.
1378
+ [provider auth migration](./provider-auth-migration.md).
1379
+
1380
+ #### Generated index storage (contributor contract)
1381
+
1382
+ Generated format 5 stores the same logical `OpenApiData` as earlier formats.
1383
+ The shared renderer interns complete parameter rows by their exact JSON text,
1384
+ including descriptions, required flags and compact schema references. Each
1385
+ operation records parameter-row ids and its unchanged serialized body. Shared
1386
+ schema definitions, response fields, lossless request graphs, operation order,
1387
+ pins and all compiler shrink options retain their existing meaning. The two
1388
+ indexes derived directly from kit declarations continue using their plain
1389
+ logical representation.
1390
+
1391
+ `compactOpenApi` exposes the exact original `details` string through an
1392
+ enumerable, replaceable property. `OperationIndex` parses definitions once per
1393
+ instance and decodes each operation row on first use. Each row occurrence gets
1394
+ its own parsed parameters; dictionary sharing introduces no new mutable object
1395
+ sharing between operations or instances. Existing shared schema references and
1396
+ their mutation behaviour remain intact. Ordinary logical indexes still use
1397
+ `JSON.parse`. Construction neither expands the complete details string nor
1398
+ computes an identity hash. Cursor binding continues hashing the same canonical
1399
+ logical data and policy, with no encoding metadata in its preimage, preserving
1400
+ existing cursor bytes for unchanged content.
1401
+
1402
+ The runtime codec uses no platform-specific imports, recursive references or
1403
+ compression dependency. Parameter ids must be nonnegative safe integers naming
1404
+ existing strings. Reconstructing a row or full details text is bounded to 64 Mi
1405
+ UTF-16 code units; full reconstruction must match its recorded exact length.
1406
+ These physical decode checks do not change request validation, schema traversal,
1407
+ search, refusal, result or discovery budgets. Generation and offline roundtrip
1408
+ tests must preserve every pinned logical contract; storage changes never accept
1409
+ new upstream evidence or golden bytes. The compaction suites cover exact strings,
1410
+ native Web Crypto identity, malformed references, lazy decoding, mutation
1411
+ semantics and every maintained provider.
996
1412
 
997
1413
  #### Value safety for REST vendors
998
1414
 
999
1415
  The bar ([decision 0005](https://github.com/zackbart/connecta/blob/main/decisions/0005-auth-selects-implementation.md)):
1000
1416
  no REST tool response, success or error, returns a credential that grants
1001
1417
  access to the vendor or a third party, or a stored secret value, unless a
1002
- reviewed named tool exists to return it. The threat model is Infisical's
1003
- above: an honest server, with secrets people type into free text out of
1004
- scope. Field-by-field redaction over a whole vendor API does not converge, so
1418
+ reviewed named tool exists to return it. The threat model assumes an honest server; secrets people type into free
1419
+ text are out of scope. Field-by-field redaction over a whole vendor API does not converge, so
1005
1420
  every vendor ships one reviewed table through one mechanism
1006
1421
  (`src/providers/_shared/rest/value-safety.ts`):
1007
1422
 
@@ -1012,12 +1427,14 @@ every vendor ships one reviewed table through one mechanism
1012
1427
  signing, webhooks, drains, transfer, claim, and invite codes). It is the
1013
1428
  union of the detectors Cloudflare and Vercel shipped first, and
1014
1429
  deliberately over-inclusive. `providers:spec` writes
1015
- `value-safety.candidates.json` beside the index when the source record sets
1430
+ `evidence/value-safety.json` (`value-safety.candidates.json` until the
1431
+ provider's step 1) when the source record sets
1016
1432
  `options.valueSafety` (vendor `operationWords`; Stripe's `expansions: false`
1017
1433
  skips id-or-object expansions, which are their own operations' candidates),
1018
1434
  stamped with the pinned digest, the detector format, and its options. The
1019
1435
  file is test evidence and never ships.
1020
- 2. **One reviewed verdict per candidate**, in the vendor's `value-safety.ts`:
1436
+ 2. **One reviewed verdict per candidate**, in the vendor's `safety.ts`
1437
+ (`value-safety.ts` until its step 1):
1021
1438
  `refuse` (the generic tools refuse it before transport, naming the
1022
1439
  operation and the reason), `redact` (reviewed field paths), or `safe`, each
1023
1440
  with a reason. Paths are dot paths into the data a tool returns: a list on
@@ -1052,10 +1469,22 @@ every vendor ships one reviewed table through one mechanism
1052
1469
  reviews the pinned spec's free-text fields for embedded vendor-minted links
1053
1470
  and records its verdict and source limitations beside its value-safety table.
1054
1471
  Stripe, Vercel, and Cloudflare use **reviewed paths plus global signing
1055
- parameters**. Literal AWS SigV4/Google V4 `X-Amz-`/`X-Goog-` Signature,
1472
+ parameters**. Known AWS SigV4/Google V4 `X-Amz-`/`X-Goog-` Signature,
1056
1473
  Credential and Security-Token values are withheld across all decoded string
1057
- values. Generic credential names (`code`, `key`, `token`, `signature`, `sig`,
1058
- etc.) and credential-looking fragments apply only to reviewed diagnostic
1474
+ values, including values written with decimal, hexadecimal or named HTML
1475
+ character references. References are read whole as value data; decoding
1476
+ only finds nested-reference boundaries and classifies a placeholder's
1477
+ remainder. No decoded replacement is returned. Possible named references
1478
+ are read fail-closed with their required semicolon and bounded HTML name
1479
+ length. Numeric references also accept an omitted semicolon, and `amp`
1480
+ may omit its semicolon immediately before `#`. Signing separators accept
1481
+ nested ampersand references around decimal, hexadecimal or named `=`
1482
+ references. Recognized name representations retain their source spelling
1483
+ and use the same fixed signing-name vocabulary and value boundaries.
1484
+ A literal query separator such as `&name=alice` still ends the signing
1485
+ value. Ordinary query strings and prose retain their original spelling.
1486
+ Generic credential names (`code`, `key`, `token`, `signature`, `sig`, etc.)
1487
+ and credential-looking fragments apply only to reviewed diagnostic
1059
1488
  paths and vendor error text. Matching destinations lose their entire query
1060
1489
  and fragment. The tables list exact operations/resource paths with evidence:
1061
1490
  Stripe charge failure/outcome messages, Vercel deployment failures and
@@ -1064,6 +1493,9 @@ every vendor ships one reviewed table through one mechanism
1064
1493
  invocation maps. The shared review harness rejects embedded-URL operation
1065
1494
  keys absent from each vendor's pinned index and invalid path syntax.
1066
1495
  The current Vercel pin was reviewed from its full response document; its unversioned URL does not retain older documents.
1496
+ Vercel project creation registers every submitted environment-variable value
1497
+ before dispatch, so echoes in response metadata, keys and prose are withheld
1498
+ before field selection while safe project and variable metadata remain.
1067
1499
  Generic parameters in descriptions, metadata, nicknames, billing footers,
1068
1500
  notes, DNS TXT, KV values and operator HTML retain their original bytes,
1069
1501
  including whole URL strings. Ordinary code/key URLs can be false positives
@@ -1095,42 +1527,85 @@ every vendor ships one reviewed table through one mechanism
1095
1527
  (decoded bytes are at most three quarters of encoded characters). Cloudflare
1096
1528
  KV additionally enforces its documented 25 MiB decoded value limit;
1097
1529
  malformed base64 and values beyond that limit refuse with `invalid_args`.
1098
- The host's inbound request cap still applies (Node `maxBodyBytes`, or the
1099
- Workers platform); response and raw-upload limits do not bound JSON fields.
1530
+ The authenticated MCP `admission.requests.maxBodyBytes` cap and any smaller
1531
+ host cap still apply; response and raw-upload limits do not bound JSON fields.
1100
1532
  JSON admission iteratively checks every key and value, with no separate
1101
1533
  depth or node-count ceiling below the accepted request's size. Cyclic
1102
1534
  direct arguments refuse with `invalid_args`; repeated non-cyclic references
1103
1535
  are inspected. The scans remain linear in the supplied and decoded bytes.
1104
1536
  Withheld values must not be written back; obtain the original
1105
- link or omit the field. A placeholder outside a destination is ordinary
1106
- text. Notion keeps its stricter reviewed-host admission and proxy policy.
1107
- Vendors whose responses embed the file links they mint can
1108
- use `withholdSignedLinks(text, hosts)` or
1109
- `withholdSignedLinksIn(value, hosts)` with a reviewed `FileHosts` policy
1110
- (`domains` and S3 `buckets`). Notion applies the latter in its `result`
1111
- hook and before mapping errors, so Markdown, async task results, paging,
1112
- named-tool results, and `select` all pass through it. The linear scan
1113
- matches only the current URL/destination's actual authority: a reviewed
1114
- domain or subdomain, or a reviewed S3 bucket, virtual-hosted or exactly
1115
- the first path segment on an S3 endpoint. A secondary scheme or `//` in
1116
- another URL's path or query cannot re-anchor it; those proxy links keep
1117
- their bytes. Schemes and slashes may be escaped. File links lose their
1118
- whole query and fragment from a literal, percent-, entity-, or
1119
- `\u`-encoded opener through the next whitespace, quote, or angle bracket;
1120
- trailing closing punctuation comes back. AWS SigV4 and Google V4
1121
- `Signature`, `Credential`, and `Security-Token` parameter values are
1122
- withheld wherever their literal signing names occur, at any host, with
1123
- plain or encoded equals signs. `carriesWithheldLink(value, hosts)` is
1124
- the shared write-admission helper: Notion refuses a write before dispatch
1125
- when a file link's own query/fragment carries the exact `[redacted]`
1537
+ link or omit the field. This link scanner alone treats a marker outside a
1538
+ destination as ordinary text. Kit admission additionally refuses whole
1539
+ marker values and any marker containment in paths and queries; longer prose
1540
+ in body fields may mention a marker (see Contributor provider kit).
1541
+ Reviewed text paths withhold credential-bearing URL queries and fragments.
1542
+ AWS and Google signing parameter values are withheld at every host.
1543
+ `carriesWithheldLink(value)` is
1544
+ a shared write-admission helper that refuses a write before dispatch
1545
+ when a link's own query/fragment carries the exact `[redacted]`
1126
1546
  placeholder, plain or up to four layers encoded. Admission resumes at
1127
1547
  each actual Markdown destination's close, including adjacent links and
1128
1548
  images and balanced/escaped parentheses; leading spaces, tabs, and one
1129
- line ending before the destination are recognized. A marker after the close is
1130
- ordinary text. This boundary is independent of redaction's fail-closed
1131
- span. Each string is scanned alone; split rich-text runs, encoded hosts
1132
- or signing names, bare hosts without `//`, and userinfo before a reviewed
1133
- host are outside this scan's threat model.
1549
+ line ending before the destination are recognized. This scanner treats a marker
1550
+ after the close as ordinary text; kit admission applies the whole-value and
1551
+ path/query rules described above. This boundary is independent of redaction's fail-closed
1552
+ span. Each string is scanned alone; split rich-text runs are outside this
1553
+ scan's threat model.
1554
+ Sent-secret redaction runs before this step, in the provider kit's parse
1555
+ pass, so a rule that decides on a value can read the `[redacted]`
1556
+ placeholder where the vendor wrote what the rule matches. Every such
1557
+ decision input fails closed through one check, `redactionAltered` in
1558
+ `src/redaction-marker.ts`. A resource or embedded-URL type discriminator
1559
+ that carries the placeholder applies every resource rule's paths and
1560
+ withheld fields, never a verbatim URL. A label (`key`, `name`,
1561
+ `target.key`) or `type` that carries it makes the record a labelled or
1562
+ typed secret, a header, cookie, or query rule, and an environment item; an
1563
+ environment list string that carries it is withheld like `NAME=value`. A
1564
+ whole string whose scheme it may have taken (no whitespace, and only
1565
+ scheme characters before the placeholder) is withheld, at a destination
1566
+ key too. A parameter name it altered (the placeholder inside a run of name
1567
+ characters that ends at a plain or encoded `=`, and not directly after an
1568
+ `=`) loses its value wherever signing parameters are scanned, and in
1569
+ reviewed text withholds the whole link, as does a placeholder opening a
1570
+ word where a link's scheme and host would be; a fragment it altered
1571
+ withholds the query and fragment. A key that carries it (text a result
1572
+ hook parses after the kit redacted it whole, such as NDJSON and log
1573
+ streams) loses its value, and its record's key-based decisions take their
1574
+ protective outcome, route header pairs included. A placeholder can also
1575
+ stand for a credential's public prefix (`sk_live_`, `whsec_`, or a signing
1576
+ parameter's name and `=`) and leave its remainder, which with the prefix
1577
+ known rebuilds the credential however short or encoded it is. Wherever
1578
+ signing parameters are scanned, a placeholder followed by any of a value's
1579
+ base64 or base64url characters (letters, digits, `+`, `/`, `=`, `-`, `_`)
1580
+ or `%` escapes is withheld with them through the end of the value
1581
+ (`splitRemainder`), so a tail of `+/` alone goes too. A reference in an
1582
+ inferred remainder counts when it decodes to credential characters or an
1583
+ ampersand. Without a semicolon, numeric digits may run into the following
1584
+ value: a credential character spelled by the first 1–3 decimal or 1–2 hex
1585
+ digits after leading zeros also counts. Other references end that
1586
+ remainder, preserving text after spacing or punctuation references.
1587
+ Known signing values read references whole regardless of the character
1588
+ they spell. A value's end never splits
1589
+ a placeholder. In reviewed links, a query component that carries the
1590
+ placeholder and has no readable name withholds the query. What
1591
+ stays visible cannot complete a credential: other punctuation alone after a
1592
+ placeholder (`.`, `,`, `;`, `!`), which no base64 value contains, and text
1593
+ past a value's end (whitespace, a quote, an angle bracket, `&`, `#`, `)`,
1594
+ `]`, a backtick or a backslash), except where that syntax is part of a
1595
+ character reference read whole. A bare numeric reference body (`#50;`,
1596
+ `#x32;`, with optional semicolons and nested ampersand continuations) can
1597
+ remain when redaction removes part of a reference. It is read as value
1598
+ data, so `#` ends a signing value only when it does not open such a body.
1599
+ Numeric fragments directly after a signing value are withheld too.
1600
+ A following parameter is decided by its own name. Value safety's own
1601
+ placeholders are not alterations: `@keys` map keys, a withheld value right
1602
+ after `=`, and a withheld `#[redacted]` fragment. More is withheld only
1603
+ when a private value occurs in a decision input, and then deliberately:
1604
+ a placeholder inside a base64 value before its `=` padding counts as a
1605
+ parameter name, so its link goes whole; an altered fragment withholds the
1606
+ query and fragment; a Content-Type redaction altered on a body that is not
1607
+ JSON leaves it unreadable (see "Contributor provider kit"); a vendor key
1608
+ spelled with the placeholder loses its value.
1134
1609
  4. **Errors.** A reviewed operation, or an unreviewed one whose request
1135
1610
  accepts a credential-named field, answers failures with the vendor's codes
1136
1611
  and status but not its text, unless its verdict is wrapped in
@@ -1143,13 +1618,15 @@ spec: every `redact` path and `keep` must name something the operation's
1143
1618
  response schema can return (through `options.valueSafety.dataRoot` for an
1144
1619
  envelope such as Cloudflare's `result`), and every resource rule path
1145
1620
  something the resource's schema can return. It stamps the paths that resolve
1146
- into the candidates file and fails on the rest, unless
1147
- `value-safety.absent.json` acknowledges them (shared verdicts applied to
1148
- operations whose schema lacks a path, or fields a schema leaves undeclared;
1149
- such paths still apply at runtime).
1621
+ into the candidates file and fails on the rest, unless the table's `absent`
1622
+ field (`value-safety.absent.json` until the provider's step 1) acknowledges
1623
+ them with a reason (shared verdicts applied to operations whose schema lacks a
1624
+ path, or fields a schema leaves undeclared; such paths still apply at
1625
+ runtime).
1150
1626
 
1151
1627
  `test/fixtures/value-safety.ts` is the shared harness each vendor's
1152
- `value-safety.node.test.ts` runs; `reviewProblems` is pure, and
1628
+ `value-safety.node.test.ts` (`provider.safety.node.test.ts` after its step 1)
1629
+ runs; `reviewProblems` is pure, and
1153
1630
  `test/value-safety-harness.node.test.ts` proves wrong paths fail it. It fails
1154
1631
  on candidates from another pin, format, or option set; an unreviewed
1155
1632
  candidate; a verdict for an operation the index lacks; a table path the stamp
@@ -1164,89 +1641,244 @@ verdict count.
1164
1641
  `RestVendor.valueSafety` is required, so a new REST vendor cannot ship without
1165
1642
  a table.
1166
1643
 
1167
- `remoteMcp({ classify })` is the public way to declare what a downstream's
1168
- tools do: `{ tools: { name: "read" | "write" | "destructive" | { verdict,
1169
- reason?, schemaDigest?, optional? } }, unlisted?: "hide" }`, validated at construction. It fails closed
1170
- (INV-1): a reviewed read fills silence but yields to an explicit write
1171
- annotation, a reviewed write stays a write whatever the downstream claims, an
1172
- unlisted tool is a read only when it says so, and a reviewed tool whose
1173
- `schemaDigest` no longer matches, or cannot be checked, is a write on discovery
1174
- and every invocation path until a release reviews it again. A digest covers
1175
- the whole schema; one too large to hash whole is unchecked. `unlisted: "hide"`
1176
- removes every unreviewed name, even an explicitly annotated read, on every
1177
- registry read, including persisted catalogs. `remoteMcp` also refuses direct
1178
- calls to unlisted names before authentication. Tool classification does not
1179
- authorize targets; providers such as GitHub check arguments and configured
1180
- scopes separately before minting credentials.
1181
-
1182
- Connectors report facts; the registry is the only classifier. A reviewed
1183
- connector carries its review as data, the deep-frozen
1184
- `Connector.classification`, and its `listTools` returns the downstream's
1185
- listing unclassified. The registry validates the field when it first reads a
1186
- connector (INV-11) and classifies the intake-redacted facts on every read into
1187
- fresh objects. Remote MCP caches the underlying SDK response before connector
1188
- decorators; decorators run again on every read. Neither a cache layer nor a
1189
- decorator holding a served or listed tool can carry a verdict. The request-scoped catalog also owns a deep copy
1190
- and returns fresh copies to discovery and invocation. Each connector call
1191
- receives a separate deep copy of its definition, so mutations cannot change
1192
- classification, schemas, or write accounting later in the same program.
1193
- A restart applies the current review to cached SDK facts. Retired registry
1194
- manifest versions 2 and 3 are ignored and expire on their original TTL.
1195
- During listings the registry counts drift against the same record. Optional
1196
- reviewed tools do not count as unserved when gated out. Reports carry an explicit
1197
- reviewed-baseline fact, counts of live reviewed tools with and without schema
1198
- pins, and complete/unavailable schema verification. A digest failure replaces
1199
- the previous observation rather than retaining a stale clean result (INV-1,
1200
- INV-6). Observation and classification share the same verification result for each
1201
- listing; classification still fails closed.
1202
-
1203
- Reports are in-memory only, weakly keyed by connector and a registry-owned
1204
- catalog partition token. The partition uses the same admitted principal,
1205
- subject, caller identity, authenticated flag and pool as catalog caching, plus
1206
- connector id and endpoint URLs. Personal registries do not share tokens with
1207
- each other or the root. Each registry retains at most 1,024 tokens; eviction or
1208
- restart yields no observation, never another subject's counts. No new storage
1209
- key or durable catalog record is introduced.
1210
-
1211
- New schema pins use `sha256-sets-v1:<hex>`. Canonicalization sorts object keys
1212
- and schema `required`, `enum`, and `type` arrays, including schema-valued
1213
- legacy dependencies. Tuple arrays and JSON data
1214
- inside defaults, constants, enum members and examples retain order. Existing
1215
- `sha256:<hex>` pins continue using the original key-sorted, literal-array
1216
- algorithm; their reviewed digests are unchanged. Migrating a legacy pin requires
1217
- reviewing the complete schemas and recording a new versioned digest. Legacy
1218
- pins may still report conservative drift for array reordering until migrated.
1219
- The iterative digest retains the whole-schema node ceiling in both formats.
1220
-
1221
- Contributor evidence: `test/provider-schema-pins.test.ts` checks every provider
1222
- construction case in the generated roster. Every pinned digest requires a
1223
- complete captured schema that passes runtime verification; changed input or
1224
- output schemas must fail it. The guard also exercises both digest prefixes.
1225
-
1226
- The maintainer `scripts/drift-check.mjs` compares published inventories with
1227
- provider evidence. Table inventories can require `inventory.accessColumn` and
1228
- record `reviewedAccess` (`read` or `write`, with destructive verdicts recorded
1229
- as write). These access snapshots must agree with the provider's runtime
1230
- classification. A documented access change or disappearance of a reviewed explicit Access
1231
- value is advisory drift, never an
1232
- automatically accepted verdict. An explicit access value makes an
1233
- `acknowledgedUnclassified` entry stale: report it and resume reporting that
1234
- unreviewed tool as an addition. Missing tools and already reviewed names also
1235
- invalidate such acknowledgements. Duplicate explicit access values must agree;
1236
- a blank duplicate does not erase an explicit value elsewhere. Missing required
1237
- columns in any tool table, unknown access values and conflicting rows are provider-local parser
1238
- findings. The internal `withVettedCatalog()`
1239
- helper has been removed. Public provider `*_VETTED_CATALOG` exports remain
1240
- deprecated aliases derived from each definition
1241
- (`test/classified-decorators.test.ts`, `test/hosted-presets.test.ts`).
1242
-
1243
- Wrappers must forward `classification` to keep the review. `{ ...connector }`,
1244
- `Object.assign`, and `Object.create` keep it. A forwarding class that omits it
1245
- serves an unreviewed connector: the downstream's annotations are its own
1246
- claims and fail closed when absent, and nothing it persists carries safety.
1247
- Phase 2's deployment-level classifier overrides, keyed by connector id and
1248
- tool ([#706](https://github.com/zackbart/connecta/issues/706)), apply
1249
- regardless of wrapping.
1644
+ Remote MCP tools classify from server annotations and deployment policy.
1645
+ Only `readOnlyHint === true` with `destructiveHint !== true` establishes a
1646
+ source read; missing or contradictory hints mean write. Deployment overrides
1647
+ in `ConnectaConfig.classification` choose effective `read`, `write`, or `hidden`.
1648
+ They cannot relax source replay safety. Effective or source write makes a call
1649
+ write-like on every resend and recovery path. Source classifications travel in
1650
+ private metadata with host-owned tool clones and stay out of persisted catalogs.
1651
+ A leftover `classify` throws and names `ConnectaConfig.classification`.
1652
+ There are no hosted review maps, pins, drift comparisons or review UI fields.
1653
+ Catalog refresh activity still compares successive accepted catalog fingerprints
1654
+ and reports only added, removed and changed counts in the caller's partition.
1655
+
1656
+ #### Characterization goldens
1657
+
1658
+ Every maintained provider with an `api()` surface has goldens under
1659
+ `test/goldens/<provider>/`: the oracle a provider migration must match byte
1660
+ for byte. `catalog.json` holds the connector metadata, the rendered guide,
1661
+ and each tool's name, description, schemas, and annotations. `calls.json`
1662
+ holds at least one fixture call per tool, then an echo call: the exact
1663
+ requests sent (method, URL, sorted headers, body), grouped into causal
1664
+ steps, and the agent-visible outcome. `failures.json` drives one read and
1665
+ one write through 400, 401, 403, 404, 409, 422, 429 (with `retry-after`)
1666
+ and 500 responses, a 401 whose message echoes every sent secret, a refused
1667
+ connection, and a transport timeout (a `TimeoutError` after the request
1668
+ left). `setup.json`, for a provider whose spec completes an OAuth consent,
1669
+ holds the consent's steps. Calls run through `Registry` and
1670
+ `InvocationService` with a real credential vault, so the outcome is what
1671
+ `call_tool` returns before MCP rendering.
1672
+
1673
+ `test/goldens/<provider>/spec.ts` says only how to build the provider and
1674
+ which calls to make, including an `echo` call whose vendor reply carries the
1675
+ text the harness passes. `test/goldens/harness.ts` owns everything else:
1676
+
1677
+ - The fake network records each request as `new Request(input, init)`
1678
+ builds it, so `init.headers` replaces a Request's headers as Fetch does,
1679
+ and a body gets its implied `content-type`. Form bodies are recorded as
1680
+ fields when they re-encode to the exact bytes sent; FormData as its parts,
1681
+ with the runtime's boundary as `<boundary>`. A JSON body is recorded as
1682
+ `json` alone only when it is exactly that value's compact serialization;
1683
+ any other spacing, key order, or escape also keeps the exact `text`.
1684
+ - A step loop drives each scenario under fake timers. It drains microtasks,
1685
+ fires timers due now synchronously, and waits for request construction and
1686
+ counted real work to finish. Newly waiting requests form one step, sorted
1687
+ by method, URL, headers, and exact body. The loop releases **one answer at
1688
+ a time**, running to quiescence after each. Previously recorded requests
1689
+ keep release priority over new requests. Fixture updates capture canonical
1690
+ cohort release order and reverse cohort release order, each with its own
1691
+ fresh module graph, clock, network and cache namespace. Queue replies are
1692
+ assigned in canonical request order in both runs; only answer delivery
1693
+ changes. Steps remain readable admission cohorts, not simultaneous answers.
1694
+ Workerd checks compare the canonical observation. In stored fixtures,
1695
+ identical observations collapse to one
1696
+ row; differing reverse observations appear in `variants`, each with
1697
+ `order: "reverse"`, complete `steps`, optional `waitedMs`, and `result`.
1698
+ Setup uses the same rule, with `{ steps, variants }` only when orders differ.
1699
+ This bounded two-order model deliberately does not enumerate all interleavings.
1700
+ - Every request records integer `after`: the number of answers delivered
1701
+ when the provider called fetch, before harness jitter or body inspection.
1702
+ It identifies a prefix of that run's release sequence; it includes auxiliary
1703
+ requests and resets for setup and invocation. The canonical run releases
1704
+ each cohort forwards, the reverse run backwards, with older cohorts first.
1705
+ For concurrent A and B, C awaiting only B and C awaiting both have canonical
1706
+ `after: 2`, but reverse `after: 1` and `after: 2` respectively. The differing
1707
+ variant preserves that dependency. One integer per request keeps frontiers
1708
+ linear, including a 300-page sequential call. A write sent before login and
1709
+ a write duplicated concurrently or sequentially also differ.
1710
+ - Distinct queued replies for indistinguishable concurrent requests are a
1711
+ fixture authoring error: the harness rejects them with a diagnostic. Use
1712
+ identical replies or a request discriminator the provider controls. The
1713
+ arrival order of identical requests cannot assign distinct responses.
1714
+ - `test/goldens/setup.ts` installs source dispatchers before a golden test
1715
+ file imports any provider, connector or Effect module. `vi.resetModules()`
1716
+ and dynamic imports refresh those graphs for each capture and release order.
1717
+ The dispatchers retain their identity across captures: saved and prebound
1718
+ functions, and functions captured by Effect, still consult the current
1719
+ capture. Their defining prototypes are wrapped too. Native implementations
1720
+ remain private to the source controller; outside captures the dispatchers
1721
+ delegate unchanged, preserving Vitest's real clocks and runner timeouts.
1722
+ - The supported asynchronous model is explicit: fake fetch answers; fake
1723
+ timeout, interval and immediate timers, including `AbortSignal.timeout`; Web Crypto's promise-returning
1724
+ subtle methods; Request/Response/Blob body reads (`arrayBuffer`, `blob`,
1725
+ `bytes`, `formData`, `json`, `text`, where present); default stream-reader
1726
+ `read`/`cancel` and stream `cancel`/`pipeTo`; and promises/`queueMicrotask` chains rooted in those sources or
1727
+ synchronous work. Effect's scheduler and sleeps use the same microtasks
1728
+ and fake timers. A synchronous TextDecoderStream pipeline is supported.
1729
+ Timer delays must coerce to finite nonnegative integer milliseconds within
1730
+ the signed 32-bit timer range; other delays poison the capture. This keeps
1731
+ Date and nanosecond clocks integral. New host asynchronous sources must be
1732
+ registered and counted before use.
1733
+ Native CompressionStream/DecompressionStream, `scheduler.wait`, and
1734
+ native socket/worker/event-source constructors are rejected explicitly,
1735
+ including when their errors are swallowed by a provider. CompressionStream
1736
+ async iteration bypasses the reader-method spy and cannot establish
1737
+ quiescence. No golden may silently bless that path.
1738
+ - A no-stimulus audit follows declared quiescence: request, counted-work,
1739
+ timer, or call-settlement progress before an answer or clock tick fails
1740
+ with an unsupported-source diagnostic. Waiting without a request, counted
1741
+ work, or fake timer also fails. The audit is a secondary assertion: source
1742
+ control starts at module load, and supported host promises and microtasks
1743
+ are counted from entry through completion. Quiescence requires zero active
1744
+ counted work, zero queued controlled microtasks, and no due timers; future
1745
+ virtual timers are explicit harness stimuli. Unregistered native producers
1746
+ are unavailable or throw, including saved constructors and swallowed errors.
1747
+ `process.nextTick`, `process.uptime`, `Atomics.waitAsync`, native messaging
1748
+ constructors, and performance timeline methods are unsupported. The
1749
+ test-only `golden-source-boundary` transform rejects Node host-module imports
1750
+ in provider graphs and rejects lazy module loading during scenario work,
1751
+ before either can capture native references or schedule uncounted I/O. Only
1752
+ the harness's fresh-graph loading phase admits test fixture imports.
1753
+ Invalid captures cancel and drain their concurrent fake requests.
1754
+ - Settle, rejected-capture cleanup, and final teardown pump controlled timers,
1755
+ microtasks, and fake requests within the original 10,000-host-turn windows.
1756
+ Only outstanding captured Web Crypto operations can extend a window: after
1757
+ exhausting it, await a snapshot of those native operations to settle, then
1758
+ resume with a fresh bounded window. Each phase allows at most 100 native
1759
+ extensions and reports an explicit error on exhaustion. Web Crypto always
1760
+ settles independently of the harness's fake network, timers, and producers.
1761
+ Body reads and stream reads are counted for quiescence but cannot extend a
1762
+ window: their producers may require a fake response, future virtual time,
1763
+ or never complete. No producer attribution or stream constructor tracking
1764
+ is needed. Pending native crypto never advances virtual deadlines or merges
1765
+ request cohorts. Clock advancement remains limited to 1,000 ticks.
1766
+ Unsupported producer dependencies reject within the turn bound; teardown
1767
+ always clears timers and releases capture ownership, including after failed
1768
+ construction and overlapping capture rejection. The next capture must work.
1769
+ Each capture has an async-context epoch that closes before ownership is
1770
+ released, even when its native extension cap is exhausted. Controlled-source dispatchers reject closed
1771
+ continuations before touching another capture's state. Fake request entry,
1772
+ admission, response delivery, and serialization check their owning epoch,
1773
+ including after asynchronous body inspection. Late work cannot record into
1774
+ or receive a response from a later capture, which never waits for it.
1775
+ Closing also attaches rejection handlers to outstanding counted promises,
1776
+ consuming abandoned native body-read rejections without awaiting them.
1777
+ Source regressions explicitly assert either completion or bounded rejection
1778
+ followed by recovery. Crypto-delay probes at 11,000 and 100,000 host turns
1779
+ must complete with unchanged observations; producer-fed bodies need not.
1780
+ - The network answers in zero virtual time. Before advancing to the next
1781
+ timer, the loop requires eight quiet real event-loop turns and an audit.
1782
+ A row records nonzero `waitedMs`, the virtual time spent on provider
1783
+ timers. Elapsed-time readings count only those timers. A timer of 0 ms
1784
+ set outside a timer callback is due at once and stays in the current
1785
+ cohort. Unjoined timers at completion fail. Since `AbortSignal.timeout`
1786
+ cannot be cancelled, remaining signal deadlines fire during teardown, outside
1787
+ `waitedMs`; any resulting request or result mutation fails. Every successful
1788
+ drive and scenario boundary has zero outstanding controlled work and timers.
1789
+ - Auxiliary routes answer token exchanges and other requests that are not
1790
+ the tool's own, and are recorded by name with headers and body. A JWT in a
1791
+ header or field is recorded as its decoded header and claims, with
1792
+ `<signature>` for the per-run signature.
1793
+ - `setup` runs before each scenario; its requests must be identical in
1794
+ every scenario and are recorded once.
1795
+ - Determinism: a fresh fake clock per scenario (timers, `Date`, and
1796
+ `performance`, and Node `process.hrtime`/`hrtime.bigint`, starting at
1797
+ 2026-01-02T03:04:05Z), counted
1798
+ `crypto.randomUUID`, and fixed `crypto.getRandomValues` and `Math.random`,
1799
+ all restarted per scenario. `create(scenario)` receives a slug unique in
1800
+ the process: the scenario name plus a per-capture suffix the goldens never
1801
+ show. Load-time hrtime dispatchers also control Effect wall and monotonic
1802
+ nanoseconds on the virtual clock. Default-date Intl formatting also reads
1803
+ the virtual wall clock.
1804
+ A process-wide cache (Google's delegated tokens) never carries
1805
+ over from an earlier scenario or capture.
1806
+ - Secrets: every credential field and secret becomes `<secret:field>`, with
1807
+ HTTP Basic shown decoded. Before that placeholdering, the harness fails
1808
+ the capture if any secret reaches a caller-visible result (INV-5). The
1809
+ guard decodes the result itself: JSON escapes, percent escapes in either
1810
+ hex case, and base64 or base64url at any alignment, up to three layers
1811
+ deep. It also finds any start of a secret that reaches 12 characters past
1812
+ its public framing, so a truncated echo is a leak. Framing is a PEM armor
1813
+ line and the DER structure that opens every key of its type (tags,
1814
+ lengths, version, algorithm identifier), which guides and example keys
1815
+ print. A later fragment, a shorter one in any encoding, or a secret with
1816
+ its case changed is not detected. A base64 run is decoded only from 11
1817
+ characters (8 bytes), shorter than the base64 of any start the guard
1818
+ looks for.
1819
+ - The echo call carries every secret not listed in `unsent` in each
1820
+ encoding redaction covers: raw, percent-encoded with upper- and lowercase
1821
+ hex, base64, unpadded base64url, and `\uXXXX` escapes with upper- and
1822
+ lowercase hex, plus the base64 of each `basic` pair. Every sent secret
1823
+ contains `/`, `+`, and `=`, so its percent and base64url forms differ from
1824
+ its raw and base64 forms. The `401 echo` case echoes the raw secrets only
1825
+ unless the spec sets `errorEcho: "all"`: a provider that caps a vendor
1826
+ message before redaction sees it would leak the start of a longer form its
1827
+ cut goes through. The Google Workspace specs set it, since Google redacts a
1828
+ message whole before cutting it to 500 characters. Their `401 echo` carries
1829
+ `errorEchoForms`: every form above, the `Bearer` and `token` framings, and
1830
+ an HTTP Basic pair holding the token as header, base64 and base64url.
1831
+
1832
+ Object keys keep the order the provider produced. A migration changes the
1833
+ provider, never the spec or harness.
1834
+
1835
+ `test/goldens.test.ts` compares every provider on Node and workerd. It fails
1836
+ if a provider folder is neither covered nor listed as remote-MCP-only, a tool
1837
+ has no call, or a golden file is missing or extra. Ordinary checks never write.
1838
+ Node compares forward and reverse observations byte for byte; workerd compares
1839
+ canonical observations. Each order starts a fresh module graph. Workers projection
1840
+ removes variants only at harness observation positions, never from vendor fields
1841
+ or schemas. Fixture updates retain both orders.
1842
+ There are no characterization tiers, path classifiers, or planning-only runs.
1843
+
1844
+ `test/goldens-harness.test.ts` keeps the source and mutation guards on Node:
1845
+ replaced Authorization headers, changed auxiliary/login bytes, writes sent
1846
+ early or twice, and missing secret-encoding redaction must alter a capture or
1847
+ fail it. Mutation probes capture the affected call rather than repeating an
1848
+ entire provider catalog. Redaction negative controls use Forms, GitHub, Breeze
1849
+ and Tithe.ly to cover OAuth/Bearer, app-token, query-key and provider-kit lanes;
1850
+ the ordinary goldens still check every provider's complete secret echoes.
1851
+ The native-extension budget is checked directly at its 100-extension boundary,
1852
+ and a gated closed-epoch continuation must fail before entering the next
1853
+ capture's network. Genuine crypto completion, rejection, late dispatch and
1854
+ producer-fed stream probes retain their native settlement coverage without
1855
+ repeating a million-turn synthetic delay. Ordinary runs use one source-probe schedule. Nightly Node runs set
1856
+ `CONNECTA_GOLDENS_DETERMINISM=1` for an identical repeat, provider jitter seeds
1857
+ 1 and 2, and the complete source-probe jitter matrix. Small native-source probes also run on
1858
+ workerd: `scheduler.wait`, CompressionStream, saved timers/microtasks and
1859
+ Effect scheduling, prebound crypto/clocks, module freshness, runner clocks,
1860
+ and Blob/TextDecoderStream promise settlement. Runtime-specific cases carry
1861
+ Vitest's `workers` tag; broad cases carry `node`, which the Workers project
1862
+ skips. Other mixed transport/auth/runtime suites use the same tags to retain
1863
+ native fetch, request/response streams, deadlines, signals, crypto and SDK
1864
+ settlement without duplicating their policy matrices. `vitest.config.ts`
1865
+ selects these files with globs that tolerate test-file deletion. Portable
1866
+ filenames alone do not enroll a new file in workerd.
1867
+
1868
+ Repository verification policy, CI requirements, and exact commands live in
1869
+ [AGENTS.md](https://github.com/zackbart/connecta/blob/main/AGENTS.md#verification).
1870
+
1871
+ JWT fixture specs import fixed, public throwaway RSA PEM keys, with an
1872
+ import/sign/verify self-test. This avoids expensive key generation on every
1873
+ fresh module evaluation without changing characterized JWT claims or bytes.
1874
+ The harness never caches evaluated module graphs. Global controls prohibit
1875
+ `it.concurrent`; parallelism must use isolated files/workers. Transformed code
1876
+ may be cached.
1877
+
1878
+ `pnpm run goldens:update [provider...]` rewrites the named goldens (all
1879
+ when none are named) on Node; it is not part of `check`. A PR
1880
+ that changes agent-facing output on purpose regenerates the affected goldens
1881
+ and says why in its description; any other golden diff is a regression.
1250
1882
 
1251
1883
  ## Optional deployment modules
1252
1884
 
@@ -1323,7 +1955,7 @@ flight's deadline, never touching the owner's signal or response.
1323
1955
 
1324
1956
  Nothing reachable from `src/index.ts` may import a `node:` builtin, so the same
1325
1957
  core runs unchanged in workerd and in Node. The Node-touching paths — `src/node.ts`
1326
- (the `node:http` adapter), `src/sqlite.ts` (`node:sqlite`), and the QuickJS process pool
1958
+ (the `node:http` adapter and its owned `http/node-transport.ts` undici dispatcher), `src/sqlite.ts` (`node:sqlite`), and the QuickJS process pool
1327
1959
  (`src/executors/quickjs.ts` plus its child) — each sit behind an explicit subpath
1328
1960
  and must stay unreachable from the root. `./auth/clerk` is separate because
1329
1961
  `@clerk/backend` is an optional peer rather than a dependency, and
@@ -1340,7 +1972,7 @@ shape from building, in someone else's repository rather than this one.
1340
1972
 
1341
1973
  ## Effect inside
1342
1974
 
1343
- The core runs on stable Effect v4 through a compatible `^4.0.0` range; no API a deployment
1975
+ The core runs on Effect v4, pinned to exactly `4.0.5`; no API a deployment
1344
1976
  touches does. `createConnecta`,
1345
1977
  `remoteMcp()`, `api()`, the `Connector` contract, and every shipped `.d.ts`
1346
1978
  are Promise-shaped and name no Effect type, so a connector author never meets a
@@ -1411,14 +2043,252 @@ storage is the root's namespaced to its principal.
1411
2043
  | Requests (`src/server.ts`, `src/routes/mcp.ts`, `src/routes/oauth.ts`) | One fiber per `fetch`, tied to `request.signal`. An `/mcp` request's admission permit and every `McpServer` it builds live in a Scope the response carries out, closed when the body ends, fails, or is cancelled, when the signal aborts, when its admitted-request lifetime ends in that request, or at once if the handler fails first. If workerd runs none of those callbacks, the next request reclaims only the expired admission record; it cannot close another request's Scope. The OAuth callback is uninterruptible: a single-use code's exchange and catalog invalidation are one commitment. |
1412
2044
  | Admission (`executor-admission.ts`, `call-admission.ts`) | Queued waiters are Deferreds settled by whoever removes them from the queue; a wait is one flat race of grant, Clock timeout, and signal. The uncontended path stays synchronous. Two controllers, as [#453](https://github.com/zackbart/connecta/issues/453) requires. |
1413
2045
  | One tool call (`invocation.ts`) | One fiber: resolution, the read-only and schema refusals, admission, and the downstream attempt sit under a single `withDeadlineEffect`, whose expiry interrupts the call wherever it is. The permit is an `acquireRelease`; the connector call is `Effect.tryPromise` over the unchanged `Connector`. |
1414
- | `execute_code` (`execute.ts`) | One fiber whose Scope owns the run's signal and executor lease, so a result, a throw, the watchdog, and cancellation all release the lease and abort the signal the same way. The executor's `acquire()` and `execute()` stay Promises raced against the signal and `execute.watchdogMs`. Each guest host call is a fiber of its own. |
2046
+ | `execute_code` (`execute.ts`) | One fiber whose Scope owns the run's signal and executor lease, so a result, a throw, the watchdog, and cancellation all release the lease and abort the signal the same way. Public executor methods stay Promise-shaped; registered built-in execution composes directly as an Effect under the signal and `execute.watchdogMs`. Queued acquisition retains its Promise request handoff. Each guest host call is a fiber of its own. |
1415
2047
  | Discovery (`catalog-service.ts`) | A request-scoped cache: one shared read per connector (`runtime/shared-read.ts`), settled by the read itself and carrying its own signal and the probe timeout whichever asker starts it. Each asker waits under its own deadline and signal, so one that times out or is cancelled fails alone, and the read is cancelled only once every asker has gone. Fan-out is `Effect.forEach` under the discovery concurrency. |
1416
2048
  | Registry (`registry.ts`) | The result stash is a program over `Storage`. Same-request catalog loads share one read the way discovery's do. Remote MCP caching uses the SDK ResponseCacheStore adapter over SQL-backed KV, with no cross-request catalog flight. |
1417
2049
  | Remote MCP (`connectors/remote-mcp.ts`) | Each request scope's state holds a Scope, each connection is a lease forked from it, and a connect in flight is a Deferred carrying the client it connected. Closing a session and the transport are each bounded to a second. |
1418
- | Downstream OAuth (`auth/downstream-oauth.ts`) | A refresh flight is a Deferred. Preparation follows caller cancellation; after dispatch, the HTTP exchange owns a 20-second deadline and the grant commit continues through the runtime deferred-work hook. |
2050
+ | Downstream OAuth (`auth/downstream-oauth.ts`, `auth/static-oauth.ts`) | A refresh flight is a Deferred. Before the first durable coordinator write (claim CAS) is submitted, call/request closure stops preparation without a CAS, token request or state change. Submission transfers renewal to the storage coordinator under its own 35-second preparation deadline and 20-second token-request/body deadline, followed by the fenced rotation commit through the runtime deferred-work hook. A closed caller promptly fails without consuming the result, marking its ledger renewed or sending/resending a resource request. |
1419
2051
  | Operator and activity data (`routes/operator.ts`) | Each JSON route is one program run by `serveOperator` behind the Promise `handle()`. Reads run under the request's signal; writes do not, so a vault write or OAuth disconnect that started reaches its cache invalidation. |
1420
2052
  | QuickJS pool (`executors/quickjs.ts`, Node only) | Each child is a scoped resource whose release sends SIGTERM, then SIGKILL after a second; crash respawn backoff is a `Schedule`. |
1421
2053
 
2054
+ ### Native registry and request services (contributor)
2055
+
2056
+ Registry catalog loading, classification, OAuth handoffs, status, invalidation,
2057
+ credential readiness, and result stashing have private Effect programs in
2058
+ `src/runtime/registry.ts`. Static class blocks install those programs while
2059
+ preserving the published Registry declaration, including its private member
2060
+ names. `registryProgram(view, method, ...args)` checks the installed method's
2061
+ identity; a custom or replaced method remains a Promise leaf. A Promise shell
2062
+ selects its original class implementation through `registryEdgeProgram`, so a
2063
+ wrapper can call the original without redispatching to itself. Catalog search,
2064
+ describe, resolution, and listing compose these programs directly. The shared
2065
+ listing is a native child of its request's Scope, started through
2066
+ `forkRequest` in the edge runner; an individual reader cannot terminate a read
2067
+ another reader still needs. Closing the request aborts and closes those children.
2068
+ Settled reads remove their request-close finalizer; active reads retain it,
2069
+ including in long-lived caller-supplied scopes.
2070
+
2071
+ Native catalog and maintained API entry points report policy, validation, and
2072
+ operation-construction throws as typed failures with the original thrown value,
2073
+ matching the Promise boundary. Native consumers can use `Effect.catch` and
2074
+ `Effect.result` without losing refusals to the defect channel. Interruption
2075
+ remains interruption. Normalization belongs at these program boundaries;
2076
+ pure catalog algorithms and core validation retain their existing behavior.
2077
+
2078
+ `RequestScope` in `src/runtime/request-scope.ts` represents the existing request
2079
+ identity. It owns call revocation, teardown, operation signals, the complete
2080
+ sent-secret set, and invocation/auth recovery facts. `openRequest` preserves
2081
+ its synchronous revocation bridge. Live context access refuses after closure;
2082
+ `recordedService` and the owner's `retained` facts remain available for
2083
+ settlement and redaction. Sharing a request identity grants no call authority
2084
+ to a spread copy of a context. Call deadlines, request lifetime, and the OAuth
2085
+ refresh coordinator remain distinct owners.
2086
+
2087
+ Maintained API programs register their original tool-handler identity through
2088
+ `src/runtime/call-entry.ts`. Invocation enters the native API validation and
2089
+ settlement pipeline, which composes a registered tool program directly; the
2090
+ Promise handler is its public shell. Replacement and custom handlers stay
2091
+ Promise leaves. Both paths retain argument validation, generic withheld
2092
+ handler diagnostics, secret redaction, call revocation, and core write-outcome
2093
+ settlement. Custom connector hooks, vault and cache adapters, Web Crypto,
2094
+ downstream OAuth, and the remote SDK retain their existing Promise boundaries.
2095
+ Cross-request call admission deliberately remains a Promise handoff so workerd
2096
+ resumes queued work in the request waiting for the permit.
2097
+
2098
+ The `Storage` service composes built-in `getNativeStorage` operations directly.
2099
+ Registry wrappers propagate that optional capability through principal,
2100
+ connector, subject, and result prefixes, including nested namespaces; list
2101
+ results strip only their own prefix. Custom stores keep the Promise fallback.
2102
+ These adapters add no retry, transaction, or storage encoding and preserve
2103
+ D1's single atomic batch and SQLite's native transaction. Native activity
2104
+ lookup remains available to the route and activity consumers.
2105
+
2106
+ ### Native executor hosts (contributor)
2107
+
2108
+ `src/runtime/executor.ts` registers private execution programs by executor or
2109
+ lease identity and the original `execute` method. `executorProgram` composes a
2110
+ registered implementation directly; custom or replaced methods remain Promise
2111
+ leaves. Public shells call their own implementation directly, allowing wrappers
2112
+ to delegate to a saved original without recursion. Native interruption retains
2113
+ the QuickJS log prefix; Promise leaves are observed even when a terminal budget
2114
+ race wins before their settlement. Redaction and write settlement remain in the
2115
+ existing execute pipeline, using its request identity and call capabilities.
2116
+ The executor seam creates no second request or call owner.
2117
+
2118
+ Both built-ins acquire their permits through native admission and return
2119
+ execute-once leases with synchronous, idempotent release. A queued standalone
2120
+ execution crosses `awaitExecutorLease`'s Promise handoff before host setup, so
2121
+ workerd resumes in the waiting request. `execute_code` retains its public
2122
+ `acquire()` handoff for that same reason. A cancelled queued handoff withdraws
2123
+ its waiter, and a late custom acquisition releases its abandoned lease.
2124
+
2125
+ The Worker SDK prepares source and dispatchers against an inert loader. The
2126
+ native host then acquires the actual Loader handle and RPC entrypoint with
2127
+ `Effect.acquireRelease`, awaits the native RPC Promise, and races the run with
2128
+ its lease's cancellation Deferred. A resource Scope owns entrypoint-before-
2129
+ Loader disposal on every exit, including never-settling evaluation. Release
2130
+ closes that Scope synchronously before returning the permit; a failed disposer
2131
+ cannot skip the next one. Forwarders lose their providers and pending waits at
2132
+ run end. Only native RPC output and the run's private host-failure map can
2133
+ supply failure provenance. Guest source isolation, intrinsic capture, import
2134
+ routing, codecs and the SDK's source-generation checks remain ordinary leaves.
2135
+ Both built-ins normalize provider leaf returns with `Promise.resolve`, retaining
2136
+ direct JavaScript callers' synchronous value tolerance and original host
2137
+ rejections. The published provider-function signature remains Promise-shaped.
2138
+
2139
+ QuickJS owns host-call fibers in each active run's Scope. Completion,
2140
+ interruption, cancellation, recycling and shutdown revoke provider lookup and
2141
+ interrupt those fibers; late SDK results and stale IPC cannot reply to another
2142
+ run. Releasing a lease during readiness or crash backoff also cancels the old
2143
+ run before its slot is handed back. Child retirement closes its resource Scope,
2144
+ sends SIGTERM, and escalates to SIGKILL after one second. Shutdown joins native
2145
+ completion Deferreds for all retirement tails. Crash backoff remains a Schedule.
2146
+ `forkOwned` in `src/runtime/run.ts` starts the host and startup-watchdog children;
2147
+ no executor starts a fiber directly. Child process events, IPC encoding and
2148
+ bounds, QuickJS VM/job pumping and guest code remain platform or pure leaves.
2149
+
2150
+ `test/executor-native.test.ts` exercises native dispatch and Worker disposal,
2151
+ never-settling evaluation/host calls, late rejection, execute-once ownership and
2152
+ original acquisition errors in workerd. `test/quickjs-pool.node.test.ts` covers
2153
+ readiness release, stale/malformed IPC, late host failures, backoff and bounded
2154
+ kill. Real Worker and QuickJS guest contracts, the HTTP Worker budget/queued
2155
+ request fixture and native-output suites supply runtime parity evidence;
2156
+ provider goldens do not execute a sandbox.
2157
+
2158
+ ### Native SQL storage (contributor)
2159
+
2160
+ `src/storage/sql-native.ts` composes KV, activity schema upgrades, retention,
2161
+ and bounded SQL copies as Effects. `src/storage/d1-copy.ts` composes the
2162
+ Workers KV copy, source-bound resume claims, verification, cutover guard, and
2163
+ recovery. Public factories and copy functions retain their Promise contracts;
2164
+ custom Promise SQL drivers retain the fallback lift.
2165
+
2166
+ `src/storage/sql-driver.ts` constructs a public Effect 4.0.5 `SqlClient`
2167
+ service inside each operation. Reads execute SQL `Statement` Effects and writes
2168
+ execute their raw operations through that service, including its connection
2169
+ borrowing, statement-transformer context, and tracing semantics. Internal
2170
+ statement transformers see the current service; no client, statement, or
2171
+ Reactivity state is cached across operations or requests.
2172
+
2173
+ Atomic batches remain one native database operation: D1's public `batch`, or
2174
+ SQLite's synchronous `BEGIN IMMEDIATE` through `COMMIT`, with rollback on
2175
+ failure. Neither path retries a dispatched write. The D1 binding's
2176
+ `meta.changes` is retained for every statement, including zero; no `RETURNING`
2177
+ payload, extra metadata statement, or D1 transaction is needed. The dependency
2178
+ is the existing exact `effect` pin. Connecta does not adopt `@effect/sql-d1`:
2179
+ its 4.0.5 batch API drops metadata and its binding contract is wider than
2180
+ Connecta's published structural interface.
2181
+
2182
+ The D.2 architecture allowance was measured against `0e314247` using cached
2183
+ official `node:22-slim` (Node 22.23.4, zlib 1.3.1-e00f703), no network, and
2184
+ read-only bundle artifacts: `/d1` grew from 62,516 to 88,305 bytes gzip
2185
+ (+25,789); the Worker example grew from 729,847 to 756,375 (+26,528).
2186
+ Final typed-driver failure normalization measures 88,319 and 756,377 bytes
2187
+ on that same official runtime. Executing real services and statement operations requires those runtime
2188
+ semantics; the earlier identity-compiler facade did not provide them. The
2189
+ explicit allowance changes only `/d1`'s cap from 62,542 to 90,000 bytes and
2190
+ the Worker example's cap from 743,000 to 760,000. Historical baselines and
2191
+ all other caps stay fixed. Final integration must remeasure the combined
2192
+ implementation and satisfy the same compatibility and privacy contracts.
2193
+
2194
+ The platform connection capability contains no request state. D1 prepares in
2195
+ the current request and retains no prepared handles or promises. SQLite keeps
2196
+ its existing connection-owned prepared-statement cache; externally supplied
2197
+ databases remain caller-owned. Schema readiness is a boolean. A microtask
2198
+ boundary precedes dispatch so pending cancellation can stop physical execution;
2199
+ a SQLite batch never yields between transaction controls. Cancellation cannot
2200
+ undo an already-submitted D1 write and never causes replay.
2201
+
2202
+ Public SQL Statement operations create spans, so SQL tracing is disabled
2203
+ before executing them. Private `SqlError` values contain fixed wording with
2204
+ no driver cause or parameters; the original thrown value is restored only
2205
+ at the private SQL boundary. Operator sinks receive no new SQL diagnostics.
2206
+ The tracing contract is checked with a recording tracer and a mutation that
2207
+ removes the guard, which must fail. Existing import and copy errors retain
2208
+ their fixed, payload-free wording.
2209
+
2210
+ `src/runtime/native-storage.ts` supplies optional `getNativeStorage` and
2211
+ `getNativeActivity` lookups. An internal consumer may compose their Effects
2212
+ without starting another fiber; an absent lookup requires the existing custom
2213
+ Promise-store fallback. Registration normalizes operation-construction,
2214
+ validation, and decoding throws into typed failures with the original thrown
2215
+ value, matching that fallback. Interruption remains interruption. The lookup
2216
+ module imports no SQL or platform implementation and does not change the
2217
+ published storage interfaces.
2218
+
2219
+ The synchronous state-file parser, validation, file read, and one atomic
2220
+ SQLite import remain native synchronous leaves because `importStateFile` is a
2221
+ published synchronous API. `openSqlite` retains file modes, WAL, busy timeout,
2222
+ and database ownership. `test/sql-effect-contract.ts` runs native-path,
2223
+ affected-row, 50-claim, cancellation, and tracing checks on SQLite and native
2224
+ D1. `test/kv-to-d1.test.ts` also reuses stores across separate workerd requests;
2225
+ the existing SQL contracts prove rollback, database-clock TTL, encodings,
2226
+ activity upgrades, and copy recovery.
2227
+
2228
+ ### Internal provider HTTP failures (contributor)
2229
+
2230
+ The kit's `src/http/errors.ts` maps every `HttpFailure` tag through
2231
+ `toConnectorCallError`. Its completeness-checked tag vocabulary is also the
2232
+ typed-client boundary's guard. Vendors declare closed public value vocabularies
2233
+ for body facts that select a failure meaning. Before sent-secret value redaction,
2234
+ the kit extracts those facts from a token-only copy and retains only declared
2235
+ values; unknown reasons or codes contribute no classification fact and fall
2236
+ back to status rules. The captured facts stay in request-local identity metadata,
2237
+ outside the failure's enumerable data. Classification reads them with complete
2238
+ bounded facts and reviewed headers. Vendors also declare each numeric source in
2239
+ `numericFacts`: an exact body `path` or lowercase `header`, a review reason, and
2240
+ an optional `format` (`integer` by default, `seconds`, `epoch-seconds`, or
2241
+ `retry-after`). The kit reads only these sources from the response before value
2242
+ redaction. It accepts nonnegative whole numbers and strings of at most
2243
+ 128 characters that contain only ASCII digits after trimming surrounding
2244
+ whitespace; `retry-after` also accepts HTTP-dates. Integers are capped at
2245
+ `Number.MAX_SAFE_INTEGER`; delay formats yield milliseconds clamped to
2246
+ 0–86,400,000. The `retryAfterMs` numeric fact supplies the kit's standard hint
2247
+ and requires an explicit delay format (`seconds`, `epoch-seconds`, or `retry-after`).
2248
+ For example, a vendor declares `{ retryAfterMs: { header: "retry-after",
2249
+ format: "retry-after", review: "HTTP Retry-After delay for failure recovery." } }`.
2250
+ Classification receives only parsed numbers, never their original text or
2251
+ undeclared fields. These facts may select a code, retryability and bounded delay;
2252
+ they never supply wording or records. Only after the verdict is fixed does wording
2253
+ receive numbers read separately from the redacted response, facts extracted
2254
+ separately from the redacted body (eight list entries)
2255
+ and, where the operation's value-safety review permits it, that redacted body.
2256
+ Pre-redaction facts never supply message text, logs, or operator records.
2257
+ Non-2xx responses always take this capture path. A connection opts in with
2258
+ `inBandRefusals: true` only when its result hook reports in-band refusals on
2259
+ successful statuses; their declared facts come from the whole response.
2260
+ Required vendor meanings for 401/403/404 precede the other defaults; 409 has no
2261
+ shared conflict default.
2262
+ Stripe declares HTTP 409 as a conflict, alongside its fact-driven 429
2263
+ `lock_timeout` rule. Its reviewed `bodyFailures` opt-in applies status-only
2264
+ classification to an oversized 424 reply, preserving the dependency-failure
2265
+ meaning when the bounded body cannot be read. The original ceiling diagnostic
2266
+ and answered status remain; the core still decides write outcomes. It does not
2267
+ apply to 401 or alter Q8. A fixed read-phase fact distinguishes failed body
2268
+ reads from malformed JSON only for vendors declaring that wording; raw
2269
+ exceptions never enter diagnostics. Other providers retain their mapping and
2270
+ wording.
2271
+ A 421 is `unavailable` and retryable only when the transport's core
2272
+ positive-read classification proves that request a read, including its actual
2273
+ method and method-override headers. The agent chooses another call; the kit
2274
+ does not resend it. This override preserves the vendor's wording and changes
2275
+ only the code and retryability. A physical write retains its existing failure
2276
+ mapping.
2277
+
2278
+ The mapping records each request's own served provenance and raw 401 facts.
2279
+ `api()` and invocation retain the sole write-outcome decision in the core's
2280
+ `providerCallFailure`/`settleWrite`. A blocked resend reports the first
2281
+ reservation's answer even for a read-classified tool issuing a physical write.
2282
+ An accepted write's blocked retry retains its original HTTP status; a slot
2283
+ occupied by an auxiliary read instead names the separate write step needed.
2284
+ Structured resend advice reaches output only when the core permits it. Generic
2285
+ unknown writes retain the core's diagnostic wait; Q8 removes it. The mapping
2286
+ adds no Promise entry or lifetime owner. The rule tables in `http-kit.test.ts`,
2287
+ Stripe's sixteen-branch comparison and `kit-core-verdicts.test.ts` establish
2288
+ these internal contracts. Maintained API providers use the kit through their
2289
+ explicit provider subpaths; it stays off the root import graph and published
2290
+ declarations expose Promises.
2291
+
1422
2292
  ### What stays plain
1423
2293
 
1424
2294
  - **Everything a deployment writes against.** The `Connector` contract,
@@ -1439,7 +2309,17 @@ storage is the root's namespaced to its principal.
1439
2309
 
1440
2310
  ### Across requests
1441
2311
 
1442
- Work shared across requests meets only through a Deferred that the owning
2312
+ Organization-mode Clerk Backend API reads share only plain membership verdicts,
2313
+ start times and sequences, active-call counters (16 per isolate, two per
2314
+ identity), and a bounded 429 backoff timestamp. They never share a promise,
2315
+ queue, signal, controller, or body across requests. A refused busy lookup is
2316
+ not cached. Each admitted read owns its ten-second deadline and cleanup;
2317
+ `finally` releases its counters after ending its upstream I/O, and the owning
2318
+ request's abort handler also releases them idempotently if workerd discards the
2319
+ pending continuation on disconnect. Membership cache
2320
+ hits precede these bounds; see [organization admission](./auth.md#one-clerk-instance-one-organization-per-deployment).
2321
+
2322
+ Asynchronous work shared across requests meets only through a Deferred that the owning
1443
2323
  request completes, never through a fiber that outlives its request. Two
1444
2324
  workerd rules follow, and both have bitten.
1445
2325
 
@@ -1481,7 +2361,709 @@ within five minutes of expiry in the same auth partition. Partial walks and
1481
2361
  auth or permission failures refuse fallback; resources require unexpired
1482
2362
  inventories, and stale fallback cannot authorize post-entry auth recovery.
1483
2363
 
1484
- ### Why not Schema or HttpApi
2364
+ ### Contributor provider kit
2365
+
2366
+ Provider declarations use `src/http/vendor.ts` for HttpApi operations and
2367
+ `src/http/tools.ts` for list, get, read, write and action builders.
2368
+ `src/http/provider.ts` composes them with `defineProvider`; it returns the
2369
+ ordinary Promise-based Connector factory. The kit stays outside the root
2370
+ graph and its Effect types are internal. Tithe.ly is the first provider on
2371
+ it (B.5): `api.ts` declares the vendor, `tools.ts` the tools, and its goldens
2372
+ are unchanged.
2373
+
2374
+ Decision 0011's standard surface (Tithe.ly's step 2) adds four kit pieces.
2375
+ A `list` without the legacy `key` returns `{ items, page }`, with
2376
+ `page.nextCursor` only while `hasMore`, and the kit appends `limit` (the
2377
+ declared default and maximum), `cursor` and `fields` to the tool's filters;
2378
+ `read` and `get` take `fields: true`. `src/http/fields.ts` narrows after every
2379
+ projection. A standard list may opt into `total: true` and supply an optional
2380
+ exact vendor count as `page.total` from its page callback. The kit validates a
2381
+ nonnegative safe integer and includes it on continuing and terminal pages;
2382
+ without a vendor count the property is absent. Lists without the opt-in keep
2383
+ their existing schema and result bytes. Never derive an exact total from a
2384
+ partial page or an estimate. `src/http/fields.ts` refuses an unknown first
2385
+ segment before dispatch; a selected
2386
+ result renders against a sparse output schema. `src/http/hatch.ts` is the
2387
+ standard hatch (`_api_search`, `_api_details`, `_api_read`, `_api_write`) over
2388
+ an `OperationIndex` with explicit verdicts. It keeps the REST family's
2389
+ argument and envelope names and validates `fields` against the operation's
2390
+ declared response roots before dispatch. Search answers stay within 16 KiB
2391
+ (at most eight compact entries, long labels cut and marked, continuation by
2392
+ cursor); details stay within 32 KiB, paging whole parameters and body
2393
+ properties, and chunking any piece larger than one answer, behind a bound
2394
+ cursor. Standard hatch search removes conversational framing; framing-only
2395
+ queries return no matches. It admits partial lexical matches alongside full
2396
+ matches. Action/read queries require all content nouns in the operation text,
2397
+ or any noun in the path/id with at least half the nouns in the text. Their
2398
+ field-only matches need half-noun coverage across text and declared field
2399
+ names. Verb-less noun phrases retain their broader rule: any noun in the
2400
+ path/id, or all nouns in the text. A leading create/add/update/edit/make/set/apply/change/rename/send/delete/remove/cancel/stop
2401
+ with an object sets write intent and retains later read words as nouns. Make,
2402
+ set, apply, change, edit and rename share update synonyms. `make a`,
2403
+ `make an`, `make new` and `set up` phrases instead select create synonyms,
2404
+ including after leading conversational framing such as `please` or `i want to`;
2405
+ `make text bold` retains update intent. Otherwise, a read
2406
+ word anywhere sets read intent, including after refund/charge/issue/process.
2407
+ Short function words and id-shaped tokens do not contribute to noun coverage.
2408
+ Sheets A1-style cell references also supply no resource noun. A write query
2409
+ whose supplied object contains no resource nouns includes its verb synonyms
2410
+ in candidate retrieval, so values do not restrict it to reads mentioning them.
2411
+ Verb-only queries and a rename destination without an object retain ordinary
2412
+ retrieval.
2413
+ Reviewed intent, the requested verb/synonym in summary/id/path, and resource
2414
+ focus sort first. A refused operation with a matching verb shares the intent
2415
+ tier only when its noun coverage exceeds every admitted operation matching that
2416
+ verb and intent. Search and details retain the original indexed summary and expose the reviewed
2417
+ reason in a separate optional `refusal` field. Ranking uses the original indexed
2418
+ text, never the newly attached reason. Search caps refusal text at 512 UTF-16
2419
+ code units without splitting a surrogate pair, independently of its 300-unit
2420
+ summary cap. A cut reason names `operations[i].refusal` in `truncated`; details
2421
+ returns the complete reason within its 32 KiB envelope or reconstructible
2422
+ `of: "refusal"` text chunks. The five long Cloudflare reasons fit whole in the
2423
+ search refusal budget, including their named alternatives. REST safety tables
2424
+ supply the reason through `restSafety`; reviewed bindings declare static operation
2425
+ refusals separately from argument validation. Discovery cursors additionally bind
2426
+ the summary/refusal representation format, so an old piece offset cannot skip a
2427
+ required parameter after this representation changes. The binding's deployment
2428
+ `cursorScope`, named suggestions and paging totals retain their existing seams.
2429
+ An unreviewed operation reports the missing hatch verdict. A refused hit has
2430
+ no read/write hatch pointer; a reviewed named alternative can still be shown.
2431
+
2432
+ Verb-less noun phrases first sort by noun coverage and an exact summary phrase
2433
+ prefix, then by an exact terminal resource: the last literal non-version segment must contain the last query noun
2434
+ or the concatenated noun phrase (`invoiceitems` for `invoice items`). An authored
2435
+ read whose vendor summary/id covers every noun shares that terminal-resource
2436
+ tier only when a real terminal-resource match exists and its description
2437
+ begins with a read action; the action word itself
2438
+ does not supply noun coverage. This preserves aggregate
2439
+ authored entry points without treating a verification action as a token list. When both candidates match the terminal resource,
2440
+ a reviewed read precedes writes and refusals. Depth with a one-segment authored
2441
+ tool preference, exact resource-noun coverage, and authored-tool preference
2442
+ break remaining ties. A pointer to another hatch receives no authored preference.
2443
+ Exact resource nouns include literal namespaces on either side of a version
2444
+ segment; parameters and version markers supply no resource coverage. This
2445
+ prefers `members` over summary-only membership matches and `tokens` over a
2446
+ verification action, while direct versioned collections retain their depth
2447
+ advantage over nested collections. For a single noun, and for
2448
+ leading `list` queries with one exact resource noun plus modifiers, collection
2449
+ ties sort by exact root, fewer nested resources, matching namespace, shallow
2450
+ depth and authored tool. Other leading `list` queries prefer authored tools
2451
+ whose summary/id covers every noun. This retains descriptive aliases while
2452
+ restoring [decision 0014](https://github.com/zackbart/connecta/blob/main/decisions/0014-api-tool-shape.md)'s
2453
+ noun-phrase preference. Person/people and mail/email aliases, s/es/ies plurals and hyphenated list
2454
+ roots share the collection keys. Sort keys are computed once per operation
2455
+ per query; lexical relevance and index order resolve remaining ties.
2456
+ Operations found only through a request or response field follow primary
2457
+ matches, and search names an authored tool whenever one serves an operation.
2458
+ A refused method outside a read/write tool's input schema is inspected through
2459
+ `_api_details` with its indexed method and path. Call validation remains closed:
2460
+ Gmail's 49 non-GET refusals do not add a method field or a write hatch, and Forms'
2461
+ refused DELETE remains outside its write enum. GitHub's refused raw PUT merge
2462
+ is likewise outside its write enum; its named guarded alternative is unaffected.
2463
+ These 51 schema refusals happen
2464
+ before authentication or I/O; details exposes the same reviewed reason as search.
2465
+ Every schema-reachable refused operation still fails with that reason before
2466
+ authentication or dispatch. This is policy inspection, never execution admission.
2467
+ `_api_read` omits
2468
+ `method` only when GET is its one reviewed read method.
2469
+ A standard list can declare `limit.values` for a vendor's discrete page sizes.
2470
+ The values must be unique positive integers within `max` and contain the default.
2471
+ The catalog declares the enum and decoding refuses other sizes before dispatch.
2472
+ A reviewed read-only POST list supplies `review`, like other reviewed POST reads.
2473
+ A reviewed OpenAPI pin may opt into `additionalResponseMediaTypes` to declare
2474
+ response fields from a vendor's nonstandard media labels. This changes field
2475
+ metadata only; the runtime still enforces its declared response format.
2476
+
2477
+ A provider may opt into `hatch.writeEnabled(options)` to omit its write hatch
2478
+ from a connection whose deployment configuration permits only reads. Discovery
2479
+ omits write-tool routing on that connection; reviewed reads do not gain write
2480
+ verdicts. The default remains enabled, and endpoint read-only policy still
2481
+ enforces every invocation.
2482
+
2483
+ The hatch's optional `pages` table declares an adapter for each indexed read
2484
+ that pages. `src/http/hatch-paging.ts` supplies Stripe forward lists
2485
+ (`has_more`, last `data` id → `starting_after`), Cloudflare
2486
+ (`result_info.page/total_pages`, falling back to `total_count/per_page/count`
2487
+ → `page`), Vercel (`pagination.next` → `until`), and GitHub (quoted or unquoted
2488
+ Link `rel=next` → `page`). Stripe opts into indexed forward lists, reverse
2489
+ lists (`ending_before`, first safe row id), v1 search (`next_page`) and v2
2490
+ lists (a confined `next_page_url` supplying only `page`).
2491
+ If a Stripe v2 URL adds query parameters the original call omitted, the read
2492
+ keeps its data and returns `hasMore: true` without `nextCursor`; a continuation
2493
+ cannot be minted without guessing which vendor defaults to preserve. Changed
2494
+ explicit filters and URLs outside the confined origin/path remain refused.
2495
+ A declaration names the cursor parameter and `query` or `body`; it must
2496
+ exist in that operation's request contract. The response remains
2497
+ `{ status, data, page? }`, with `page: { hasMore, nextCursor?, param, in }`.
2498
+ With parameter-based continuation, put the opaque `nextCursor` back in the named
2499
+ parameter. The hatch binds it
2500
+ to its tool, content-hash index revision, method, path, other arguments and selected fields,
2501
+ canonicalizing nested object key order while preserving array order and scalar types.
2502
+ It decodes the cursor before index validation and sends only the vendor value. Paging is
2503
+ read after connection safety and before field selection; result hooks must
2504
+ retain the paging metadata. Only an adapter's explicitly requested response
2505
+ headers enter its answer, already sent-secret redacted. GitHub extracts just
2506
+ the page from a Link URL with the same origin and unchanged filters, including
2507
+ GitHub's repository-ID paths; it never follows the URL or changes the original
2508
+ request path. Filter comparison uses parsed scalar parameter multisets,
2509
+ preserving duplicate counts and tolerating order and URL-encoding differences.
2510
+ Adapter continuations use the same root-store HMAC key and admitted
2511
+ connection/personal-owner binding as discovery cursors. The signed v2 token's
2512
+ claims preserve the vendor value's string/number type and pass the request-secret
2513
+ encoder. An unpaged hatch keeps its existing result schema.
2514
+ The optional `pageCursor: "argument"` declaration separates a top-level
2515
+ signed `cursor` from raw first-request anchors. Stripe allows a raw
2516
+ `starting_after` or `ending_before` record id in `query` only without cursor;
2517
+ both directions or a cursor with any raw anchor are refused before dispatch.
2518
+ Returned tokens authenticate the selected adapter parameter and value as well
2519
+ as the call. Search/v2 raw page tokens cannot replace cursor. Providers without
2520
+ this declaration keep parameter-based continuation.
2521
+ An explicit initialAnchor adapter on parameter-based paging can retain a numeric
2522
+ timestamp as a first-page time-window anchor; opaque string continuations still
2523
+ require a signed token.
2524
+ The generated Paging convention follows the read tool's input schema: tools
2525
+ that take `cursor` restore `page.nextCursor` as that top-level argument; tools
2526
+ without it restore the token in `query[page.param]` or `body[page.param]`, as
2527
+ `page.in` specifies. `kit.hatch.paging` still validates declared mode values as
2528
+ a sanity check; it does not select the generated continuation instruction.
2529
+ An unsafe continuation keeps the safe page data with `hasMore: true` and no
2530
+ `nextCursor`.
2531
+ A reviewed `HatchBinding` can supply vendor encoding/origin routing through
2532
+ the same Connection API; the shared hatch retains resolution, verdicts,
2533
+ index/fields validation, withheld-value admission, paging and projection.
2534
+ A binding may resolve configured query routing before validation and signed
2535
+ cursor binding. Vercel uses this to apply its teamId default and deliberate
2536
+ teamId/slug/null overrides consistently with named tools and uploads.
2537
+ A binding may opt into `truncated: true` to supply reviewed size-cut paths
2538
+ (such as `data.content`) in its answer. The hatch then declares and propagates
2539
+ an optional top-level `truncated: string[]` on reads and writes. Without the
2540
+ opt-in, the schemas and results are unchanged; size-cut metadata is not inferred
2541
+ from vendor booleans or added to other providers. A separate `warnings: true`
2542
+ opt-in declares and preserves reviewed `warnings: string[]` beside `data`, even
2543
+ when `fields` narrows the vendor body.
2544
+
2545
+ A binding may expand deployment-owned path defaults (Cloudflare) or canonicalize
2546
+ repository contents paths (GitHub) before indexed resolution, and may prepare a
2547
+ reviewed text/NDJSON body before validation. Both the original
2548
+ and resolved paths pass withheld-value admission. Closed request-header enums
2549
+ are opt-in; selected headers join the paging cursor's call identity. Text body
2550
+ inputs are opt-in too. A provider with bounded call-owned scope lookups declares
2551
+ its fixed write steps, preserving the resource call's single-attempt slot.
2552
+ A page adapter may declare `cursorInputMessage` to give provider-specific repair
2553
+ guidance when an unsigned raw value is supplied; absent keeps the standard refusal.
2554
+ An optional `fieldsRoot` supplies vendor-envelope selection guidance and the
2555
+ corresponding generated convention; it never changes the declared response roots.
2556
+ Field selection on every provider, including named reads, accepts optional `[]`
2557
+ array notation (`result.id` and `result[].id` are equivalent); this normalization
2558
+ is shared behavior, independent of `fieldsRoot`.
2559
+ Bindings retain the hatch's other-server and JSON/form body-type guards by
2560
+ default. `allowOtherServers: true` and `allowOtherBodyTypes: true` explicitly
2561
+ replace those guards with the binding's reviewed routing or encoding checks.
2562
+ Stripe uses it for nested v1 form parameters, v2 JSON, account/context headers
2563
+ and the confined PDF origin. An optional hatch idempotency declaration uses
2564
+ the kit write builder's checked key hook: Stripe POST writes take or generate
2565
+ keys, while DELETE and the read-only invoice-preview POST send none.
2566
+ Redacted or withheld cursor values are refused both before minting a continuation
2567
+ and after restoring a decoded continuation, before dispatch. The shared
2568
+ `refuseWithheldValues` check runs on the restored query/body: paths and queries
2569
+ refuse marker containment, while bodies retain the whole-value/link rule.
2570
+ `src/http/vendor-index.ts` derives that index from a kit vendor's own
2571
+ declarations when the vendor has no pinned OpenAPI document: `form()` request
2572
+ contracts, `Summary` annotations and lenient success schemas become the
2573
+ index's parameters, bodies, summaries and declared fields (optional
2574
+ `OpenApiData.fields`). Kit facts carry the vendor's declared
2575
+ `VendorErrors.meanings` and an `idempotencyKey` checked against the write
2576
+ builders at construction, which the generated Conventions state.
2577
+
2578
+ Hatch request paths and query keys/values refuse any containment of the shared
2579
+ `[redacted]` marker, including decoded spellings. Body fields and kit write
2580
+ arguments refuse a string that equals the marker after decoding, or carries a
2581
+ withheld link (including origin-only links ending in the marker). Longer free
2582
+ text such as `the log says: Authorization: Bearer [redacted]` is accepted.
2583
+ Admission iteratively visits keys and values before the write program runs;
2584
+ it checks agent arguments, not whole bodies later composed from internal
2585
+ reads. The kit connection also checks actual typed path parameters and query
2586
+ values before transport. Legacy non-kit tools retain their link-only admission;
2587
+ arbitrary binary/base64 bytes are not decoded by this scalar rule. The kit
2588
+ sets `refusesWithheld` for the standard hatch, enabling the generated convention.
2589
+ Declared error-code facts use the closed `ConnectorCallErrorCode` set at
2590
+ construction (INV-11); classification callbacks are not run at construction.
2591
+
2592
+ Bound cursors MAC a canonical tuple of version, admitted connection id,
2593
+ personal registry's credential-owner partition (null for shared registries),
2594
+ canonical tool/argument digest and continuation payload with HMAC-SHA-256.
2595
+ The canonical envelope spelling is required too. Hatch discovery binds a
2596
+ SHA-256 content hash of the index (operations, details, fields and routing
2597
+ metadata), alongside its declared label; the displayed revision label remains
2598
+ unchanged. Index edits invalidate old continuations even without a version
2599
+ bump. Search offsets, details pieces/chunks and surrogate boundaries are
2600
+ validated. Tampering or a changed scope/index fails `invalid_args`; changing
2601
+ only `limit` remains allowed.
2602
+
2603
+ One random 256-bit signing key is initialized lazily with compare-and-set
2604
+ in the durable root `continuationKeys` family. Only resolved, nonextractable
2605
+ keys are cached per store; no request promise or key value is logged. Registry
2606
+ `rootStorage` preserves the explicit deployment root across personal partitions
2607
+ independently of `catalogStorage`; standalone API connectors bind their own id
2608
+ and use their supplied store. Every deployment resolves a store, so no unsigned
2609
+ fallback exists. SQL-backed cursors survive isolate/process changes; memory
2610
+ storage loses them with the store. Key-load outages or corrupt stored keys
2611
+ produce retryable `unavailable`, naming the cursor signing key without its
2612
+ value. See [cursor-key recovery](./deploying.md#recover-a-cursor-signing-key):
2613
+ after repairing/deleting a corrupt key, restart every process/isolate to clear
2614
+ cached keys. Pre-upgrade cursors require a new first page. Offline goldens
2615
+ preseed a fixed key only in their harness.
2616
+
2617
+ The helper derives connection metadata and probe messages from
2618
+ validated options and still accepts complete legacy usage guides for compatibility.
2619
+ All maintained providers have completed the standard-surface migration.
2620
+ Candidate checks can refuse before authentication while
2621
+ preserving the provider's exact diagnostic. Its optional `authScope(options)`
2622
+ selector fixes OAuth grant ownership from deployment mode at construction;
2623
+ CCB uses shared System Auth grants and personal Identity Auth grants. Tool
2624
+ arguments cannot select a grant's owner. On Node, every maintained-provider
2625
+ request uses the owned sender `listen()` installs, reads included. Without it,
2626
+ maintained-provider writes and reviewed POST reads are refused before sending;
2627
+ suites exercising those requests configure the sender themselves, as the goldens
2628
+ harness does. Anything a connection
2629
+ caches across calls, such as its typed HttpApi client, is built with no
2630
+ services, because Effect constructors close over the context they run in and a
2631
+ cached value would otherwise keep its first caller's request alive (INV-7).
2632
+
2633
+ The optional `grant(options)` factory supplies a renewable machine-login
2634
+ `Grant`, exclusive with static OAuth. It is built outside caller contexts;
2635
+ each send binds the active call to its token, refresh and rejected effects.
2636
+ `settleWrite` decides the outcome and handoff.
2637
+ Reads replay once after renewal; writes are never resent.
2638
+
2639
+ A machine grant may also declare `grantRenewal` modes, with an optional
2640
+ `Grant.discard(token)` that forgets exactly that refused token and keeps any
2641
+ newer one. `replayRejection: "answer"` discards the token a replayed read was
2642
+ refused with and hands that 401 to the vendor's status table, so the message
2643
+ comes from the bounded, sent-secret-redacted body instead of `rejected`.
2644
+ `writeRenewal: "discard"` discards a write's refused token without renewing:
2645
+ no token request is sent, nothing is recorded as renewed, the write's 401 is
2646
+ answered, and `settleWrite` decides its outcome and handoff. Neither mode
2647
+ resends a write, and the defaults are unchanged. Google Workspace uses both,
2648
+ because its delegated tokens are minted per call and a refused write only
2649
+ needs the next call to mint again.
2650
+
2651
+ All Google Workspace products use `src/providers/_shared/google/kit.ts`, which
2652
+ puts the delegated service account on a machine grant. `workspace.ts` owns
2653
+ shared options, connection parsing and subject resolution; `service-account.ts`
2654
+ owns token exchange and caching. JSON requests use `googleClient`, and
2655
+ Drive's `driveClient` adds binary and charset-aware text projections over
2656
+ that adapter. The subject resolves once per call from deployment config and
2657
+ the admitted caller, and the private token exchange keeps its in-memory
2658
+ cache, so only token strings and expiries cross requests. Each product
2659
+ declares its operations, and a value-safety resolver refuses any path it does
2660
+ not declare. Revision-guarded writes set the
2661
+ `revisionGuarded` request flag that Google's error table reads. The completed
2662
+ product migrations use Effect-declared named tools and indexed
2663
+ hatches. Fixed write steps are snapshotted at construction. Docs declares a
2664
+ create and its optional initial-text edit as separate steps, each sent at most
2665
+ once.
2666
+
2667
+ All six Google Workspace products use the shared standard-surface support.
2668
+ `scripts/google-discovery.mjs` converts
2669
+ archived exact-byte official Discovery documents into deterministic OpenAPI.
2670
+ The primary vendored pin retains the official URL, revision and source digest;
2671
+ its `discovery` relation names the sibling source pin and normalizer format.
2672
+ Generation and offline freshness reproduce the conversion, and the generated
2673
+ module records its independent document digest. Full source method inventory
2674
+ and canonical source-to-relative-path mappings survive normalization. Gmail's
2675
+ source `userId` becomes deployment-owned `me`; runtime paths are relative to
2676
+ the existing connection root. Reserved Slides ids remain one encoded segment.
2677
+ Flat-path aliases are reconciled with the declared path parameters. Method-
2678
+ specific required annotations specialize only affected reachable input schemas,
2679
+ including nested and recursive references. Deprecation metadata is retained;
2680
+ compact details opt in with `deprecations: true`. Live Discovery drift compares
2681
+ sorted-key JSON and revision, independently of archived-byte integrity.
2682
+ Only safe JSON query globals enter the contract. Source bytes are contributor
2683
+ evidence and never runtime imports.
2684
+
2685
+ An index can retain `requestSchemas`, a lazy referenced graph for complete
2686
+ request details, independently of the compact depth-bounded admission checker.
2687
+ Details attach the reachable `$defs`, preserving recursion, required fields,
2688
+ dynamic maps and parent schema metadata through the existing bounded
2689
+ continuations. This does not make the compact checker a full JSON Schema
2690
+ validator. Product admission still needs literal reviewed verdicts and
2691
+ independent batch-kind, revision and resource guards. Google indexes explicitly
2692
+ set `pathMatching: "uri-template"` on `OperationIndex` to handle encoded literal
2693
+ collisions and parameter action suffixes. Other indexes retain exact literal
2694
+ segments and their existing refusal ordering. The matching mode also binds
2695
+ discovery cursors. An indexed `googleClient` requires each named request's declared
2696
+ template and refuses a substituted id resolving to another operation before
2697
+ grant evaluation. Products opt in with `googleClient(connection, title,
2698
+ { standard: true, index })`; their requests carry `template`.
2699
+ Both the client and `googleHatchBinding(connection, { index, review })` require
2700
+ the explicit URI-template mode at construction. The hatch requires the binding's
2701
+ exact index identity. Standard clients and Google bindings reject a missing
2702
+ index with an explicit construction error. The provider-standard suite checks
2703
+ actual hatch construction through every Google product fixture, requiring its
2704
+ Google binding with the same connection and index. The Google binding
2705
+ additionally refuses percent-encoded unreserved characters and colons before
2706
+ dispatch.
2707
+
2708
+ The generator's `allSuccessResponses: true` option unions JSON response fields
2709
+ from every 2xx. Existing providers keep the first JSON success contract until
2710
+ their own reviewed regeneration; no committed provider index changes here.
2711
+
2712
+ `src/providers/_shared/google/hatch.ts` supplies JSON framing over the existing
2713
+ confined `Connection.sendPath`. It retains the hatch's origin/media guards,
2714
+ delegated grant, safety and field-selection pipeline. Its optional `wrapSend`
2715
+ receives a detached copy of the admitted request (including its body) and a
2716
+ zero-argument inner send. The binding retains its original `send` function;
2717
+ wrappers run only after hatch admission and can dispatch the captured request
2718
+ at most once. A second send is an internal defect; after a write dispatch its
2719
+ outcome remains unknown. Opting into this wrapper also enables envelope-level
2720
+ warnings, so request-dependent save advice survives field selection without
2721
+ changing vendor data. Mutating the inspection copy cannot change its path, body,
2722
+ query, revision flag or safety policy. Products use this seam for response
2723
+ semantics that depend on the request, such as independent comment saves.
2724
+ The `googlePageToken`
2725
+ adapter is separately opt-in by exact list key with `pageCursor: "argument"`.
2726
+ It treats missing/empty `nextPageToken` as terminal, rejects malformed tokens,
2727
+ and continues empty pages when a nonempty token exists. `query.fields` is a
2728
+ Google vendor mask; the adapter preserves `nextPageToken` on the wire. The
2729
+ token must be selected as a complete top-level field; `nextPageToken/x` does
2730
+ not select it. The top-level `fields` argument narrows safe result data only after paging. Omit
2731
+ `pageToken` initially and restore the signed `page.nextCursor` as `cursor`;
2732
+ the raw token in safe vendor data is not accepted as continuation input.
2733
+ For example, a direct value-mode result has outer `result.data`, the hatch's
2734
+ `result.data.page.nextCursor`, and vendor JSON at `result.data.data`:
2735
+ `call_tool({ address: "crm.forms_api_read", args: { path:
2736
+ "/forms/form_1/responses", cursor: previous.data.page.nextCursor },
2737
+ resultMode: "value" })`. This is an envelope illustration for an opted-in
2738
+ provider, not a claim that the Forms migration has shipped.
2739
+
2740
+ `googleErrors(product, { standard: true })` declares the product's 404 meaning
2741
+ and uses common status fallbacks; revision-conflict meanings require a
2742
+ separate declaration. Standard-mode `googleClient` preserves failure phase
2743
+ metadata for partial-create recovery while leaving settlement and wording to
2744
+ the kit. Its incomplete-body wording covers JSON and binary reads without
2745
+ claiming that binary success was expected to be JSON. Legacy mode retains
2746
+ its catalog and failure bytes. No shared-core auxiliary-write outcome change
2747
+ is part of this opt-in support.
2748
+
2749
+ Drive's two-request reads also declare fixed steps: `get_file_content` uses
2750
+ `metadata` and `content`, and non-root `list_folder_items` uses `folder` and
2751
+ `files`. These reads still send both GETs when deployment classification
2752
+ serves them as writes; each step can leave only once.
2753
+ Gmail declares separate initial and body-download steps for `get_message`,
2754
+ `get_draft`, and `get_attachment`; `search_threads` and `list_drafts` name
2755
+ each of up to 25 summary reads, and `get_thread` names each stored body in
2756
+ Gmail's 100-message conversation limit. Serving these reads as writes keeps
2757
+ every request available once.
2758
+ The layer's request adapter keeps the legacy wording of a served write whose
2759
+ answer proves nothing (no answer, a 5xx, a redirect, or an unreadable 2xx) and
2760
+ reports how far a failed request got. The core still decides every write's
2761
+ outcome. Gmail's auxiliary draft and reply lookups pass both `effect: "read"`
2762
+ and a construction-declared step: the guarded client's early reservation uses
2763
+ that named slot, while the ledger marks the request auxiliary. A failed lookup
2764
+ keeps its read verdict and cannot make an unsent draft write unknown.
2765
+
2766
+ Sheets declares its seven API methods in `src/providers/sheets/api.ts`; its
2767
+ kit tools retain the private digest-bound cell cursor, legacy A1 path encoding,
2768
+ and create/append recovery advice. Its projections and the Google failure
2769
+ classifier read fields by the names Google gave them, which the kit's parse pass
2770
+ keeps. Its catalog and guide stay unchanged in step 1.
2771
+
2772
+ Google's request adapter also offers a reviewed binary send. It marks the
2773
+ operation `googleBytes` for the product's `binaryBodies` selector and forwards
2774
+ an optional `maxBytes` to the success-only prefix reader. It returns the
2775
+ connection's safe answer: bytes pass the existing sent-secret encoding before
2776
+ any result hook, then value safety before the product reads them. Products
2777
+ keep charset decoding and special error-status interpretation local.
2778
+
2779
+ An optional `tagConnector(connector, options)` records construction facts
2780
+ about the built connector, once, before the registry can serve it. Its return
2781
+ value is ignored, so it cannot replace or wrap the connector. Google Workspace
2782
+ uses it to tag whose account the connector acts as, which the registry reads to
2783
+ hide a mapping-subject connector from agents in a pool without an owner.
2784
+
2785
+ GitHub's tools and allowlisted indexed hatch use kit read/write builders and a confined
2786
+ Connection while its App authentication and scope-verification transport stays
2787
+ private. Its tokens are partitioned by App/key, installation, repository IDs,
2788
+ permissions and purpose. A resource 401, or a 403 without rate-limit evidence,
2789
+ invalidates the exact bearer even when its body cannot be read. The next call
2790
+ mints lazily; there is no inline renewal or replay and no renewable Grant.
2791
+ Only completed scalar cache data crosses requests; private signing imports
2792
+ and all transport operations remain request-owned.
2793
+
2794
+ The optional `probe.run(connection, options)` program replaces the operation
2795
+ probe when authentication needs a different read or a private login transport.
2796
+ It is built once at provider construction outside caller contexts, using the
2797
+ confined Connection and validated options. Execution uses the same anonymous,
2798
+ uncharged candidate services, accounting, settlement, failure mapping and
2799
+ finalizer. An empty write-capacity map refuses Connection writes before dispatch.
2800
+ Every defect from a run probe or its hooks uses the generic withheld notice,
2801
+ even without a kit dispatch, because a private transport leaves no kit visibility
2802
+ evidence. An open operation probe retains its original defect.
2803
+
2804
+ The REST bridge in `src/providers/_shared/rest/kit.ts` retains pinned index
2805
+ validation and legacy hatch schemas while the kit owns tool leases, fixed write
2806
+ capacity, transport, failures and result safety. It verifies schema equality
2807
+ before restoring legacy catalog key order. `indexedVendor` derives operation
2808
+ metadata from the index and builds the full manifest lazily; the typed client
2809
+ contains only named operations and probes. Bulk indexed declarations preserve
2810
+ the OpenAPI brace paths verbatim and carry explicit parameter Schemas. They
2811
+ serve the manifest only: typed sends refuse an undeclared operation name,
2812
+ and indexed hatches dispatch through `sendPath`. Parameter names outside
2813
+ `[A-Za-z0-9_-]+` fail construction. Manifest/index operation-set equality is
2814
+ checked independently of drift digests, which still use full vendor contracts.
2815
+
2816
+ A tool may opt into `inputOrder: "original"` when legacy request bytes depend
2817
+ on argument insertion order. After normal successful decoding, the builder
2818
+ copies only decoded values in the original key order, recursively through
2819
+ objects and arrays. Dropped keys stay dropped and decoder-introduced keys
2820
+ append at the end. Capacity selectors, idempotency and the tool program all
2821
+ receive that reordered decoded value. The default preserves the decoder's
2822
+ existing order; the option retains no original values and skips no validation.
2823
+
2824
+ The kit write builder emits `readOnlyHint:false` and its explicit `destructive`
2825
+ verdict as `destructiveHint`; `destructive:false` describes an additive write.
2826
+ A reviewed `idempotent` declaration emits `idempotentHint` independently of a
2827
+ transport idempotency key. These hints describe behavior; write routing and
2828
+ endpoint restrictions still apply. For example, a tool that only creates a new
2829
+ note can declare `destructive:false` without promising idempotency.
2830
+
2831
+ `restKitTools` may declare a `writeSteps` map whose values are `"once"`.
2832
+ Construction freezes the map; every named step sends at most once and an
2833
+ undeclared step is refused before dispatch. Omitting it retains the single
2834
+ implicit write step. Auxiliary ownership reads in a write call retain that
2835
+ call's write classification and consume their declared lookup steps, so the
2836
+ resource write cannot be resent.
2837
+
2838
+ Reviewed response policies can select text using only the redacted, normalized
2839
+ Content-Type. A separate `binaryBodies` review opts into bounded byte reads.
2840
+ Only the connection sees those bytes: it calls `RequestSecrets.encode` before
2841
+ constructing the legacy `{ contentType, bytes, base64 }` value. Undeclared
2842
+ binary bodies retain the JSON-only failure. Fixed secondary origins use a
2843
+ separate kit Connection built during provider construction, with their own
2844
+ confinement and the same authentication and result safety.
2845
+
2846
+ A definition may derive authentication and default admission from validated
2847
+ options. Probes may derive their request from those options and describe a mapped
2848
+ failure (for example, an accepted key lacking account-read permission). Public
2849
+ routing/version headers remain request or constant headers, never authentication
2850
+ secrets. An optional positive `timeoutMs` retains a vendor's request and body
2851
+ deadline. A path request opting into `emptyForm: true` retains an explicitly empty form on the wire; other empty form declarations keep their previous omission.
2852
+
2853
+ `PathRequest.rawBody` accepts a string, `Uint8Array` or `FormData`; it is
2854
+ exclusive with JSON, form, `emptyForm` and item/bulk identities. Conflicting
2855
+ combinations fail construction where possible and otherwise refuse before
2856
+ dispatch. Framing uses Effect's text, byte-array or form-data body helpers.
2857
+ An explicit Content-Type is preserved exactly; multipart callers omit it so
2858
+ fetch generates its boundary. This field does not change classification,
2859
+ write-step accounting or the sent-secret set, and existing size ceilings apply.
2860
+
2861
+ A send may opt into a success-only `prefix` read with a byte ceiling, an optional
2862
+ tighter time limit and a boolean chunk-stop callback. Failure responses retain
2863
+ the ordinary bounded status and withheld-error path. Early success cancels the
2864
+ unread stream and reports `prefix: { truncated, bytes, stopped }`; accepted bytes
2865
+ still pass sent-secret redaction, the result hook and value safety before any
2866
+ projection. The callback runs with an empty Effect context, receives a copy of
2867
+ the chunk, and cannot transform the answer. Its defects receive the same
2868
+ withheld-text rewrap as result hooks. Invocation cancellation remains authoritative.
2869
+ Reviewed binary prefixes retain bytes through the same sent-secret encoding
2870
+ as whole binary bodies, without a text decode. Only that binary path accepts
2871
+ a zero-byte prefix, which keeps empty bytes and the ordinary prefix facts.
2872
+
2873
+ CCB's static OAuth bridge opts into `oauthWrite401: "grant"`. An optional
2874
+ `refreshOnce` mapper receives only renewal facts and reuses the core's
2875
+ `unsentAfterUnauthorized` diagnostic, preserving the grant's authorization
2876
+ message and the message for a renewed credential whose write was not replayed.
2877
+ The error retains the resource dispatch's provenance; `settleWrite` decides
2878
+ the final outcome. The default middleware behavior and renewal ownership stay
2879
+ unchanged.
2880
+
2881
+ The static compatibility option caps discarded 401 challenges with the shared
2882
+ OAuth challenge limit before renewal. A guarded client's optional
2883
+ `maxUnauthorizedResponseBytes` applies to declared and streamed bodies;
2884
+ `read(response, maxBytes)` can narrow a read's ceiling further. The middleware
2885
+ maps an oversized challenge using its own recorded dispatch, including a
2886
+ declared-size refusal before the response wrapper exists. It never renews
2887
+ after that refusal.
2888
+
2889
+ The kit's parse pass redacts every value of a parsed JSON body and keeps its
2890
+ object keys. Value safety drops and redacts fields, and projections navigate
2891
+ them, by the names the vendor gave them, so a private argument sharing part of
2892
+ a name such as `client_secret` or `paragraph` cannot hide the field from the
2893
+ rule that withholds it or erase the structure a projection reads. A result
2894
+ hook that redacts again does so the same way. Text, binary bodies and JSON
2895
+ embedded inside a string are values and retain ordinary redaction. Response
2896
+ headers keep their names, which `Headers` must accept, and their values are
2897
+ redacted together. Keys are checked once value safety and the projections have
2898
+ run: the `api()` boundary and the invocation apply payload rules to the
2899
+ connector's result and failures, keys included, before the agent boundary's
2900
+ pass. Until then a key is the vendor's text, so a provider that serializes part
2901
+ of a body and cuts it redacts the serialized text first (`redactBeforeCut`), as
2902
+ Breeze's in-band failure text and Slides' raw chunks do. A private value of four
2903
+ to seven units that a vendor echoes only as an object key is therefore matched
2904
+ once, at the connector boundary; the agent boundary's envelope rules do not
2905
+ match values that short.
2906
+
2907
+ Redacted values can still be decision inputs. `textBodies` and `binaryBodies`
2908
+ see a Content-Type that redaction altered as `application/json`, so no altered
2909
+ type routes a body around ResultSafety as one string or as bytes; a body that
2910
+ is not JSON is then unreadable. Value safety's own decisions on values fail
2911
+ closed the same way (see "Value safety for REST vendors").
2912
+
2913
+ For an unreadable successful body, the shared mapper appends an unknown-outcome
2914
+ and check-before-repeating sentence for the call's own write request, including
2915
+ writes transported with GET. An auxiliary read lookup inside a write call
2916
+ keeps its read message; a failed lookup does not establish that the write left.
2917
+ `VendorErrors.unreadableWrite` can override the complete write message.
2918
+ Reads keep their existing wording. Overflow's
2919
+ unreviewed text responses use its write override; its reviewed health text and
2920
+ refund acknowledgement remain accepted.
2921
+
2922
+ A vendor may declare `encodedFields`: exact dotted JSON paths with whole
2923
+ array segments (`payload.parts[*].body.data`), `encoding: "base64url"`, an
2924
+ optional `sizePath` addressing the same array elements, and a one-line review.
2925
+ Construction freezes the declaration. Before ordinary whole-answer redaction,
2926
+ the kit decodes each valid field and redacts its decoded text and bytes through
2927
+ the same request-secret service. Unchanged fields retain their original
2928
+ spelling; changed fields retain the original alphabet and padding style when
2929
+ re-encoded. Both standard and URL-safe alphabets are accepted with valid padding. Invalid base64url stays on
2930
+ the ordinary redaction path. A declared size is checked against the original
2931
+ decoded byte length first; a mismatch also retains ordinary redaction. Size
2932
+ values stay unchanged. This never exempts the encoded field or its surrounding
2933
+ JSON from the generic redaction pass.
2934
+
2935
+ Result hooks alone may call `encodedFieldProvenance(answer, concretePath)`.
2936
+ Its private WeakMap is keyed by the parsed, redacted answer; each frozen
2937
+ entry holds only `originalLength`, optional `sizeValid`, and `changed`.
2938
+ Provenance contains no content and is never serialized into facts, messages
2939
+ or logs. Gmail copies this metadata privately for its decoded body containers:
2940
+ text renders after redaction and before cuts, while changed binary content is
2941
+ withheld in full. Its original byte counts still govern attachment validation.
2942
+ Vendors without this declaration retain the existing parsing path.
2943
+ Gmail refuses to update a draft whose Content-Disposition or any header name
2944
+ was redacted, because it cannot prove that rebuilding the message would retain
2945
+ its headers and attachments.
2946
+
2947
+ Credential descriptions may derive from validated connection options, and a
2948
+ probe may reject a successful answer that identifies the wrong account.
2949
+ Transport diagnostics use the vendor error label rather than the provider
2950
+ definition's title. A vendor may declare reviewed `textBodies` by status and
2951
+ operation: `text` retains decoded UTF-8 and `json-or-text` parses JSON with a
2952
+ text fallback. Other responses still require JSON. The bounded byte reader,
2953
+ sent-secret redaction, and withheld-error policy apply before text can reach
2954
+ vendor wording or a result hook; a legacy hatch's text contract does not admit
2955
+ malformed JSON on named tools.
2956
+
2957
+ The helper's connection-specific `transport(options)` supplies constant
2958
+ headers and final-URL admission outside any caller context. Admission receives
2959
+ the request method and encoded text body before authentication, so product
2960
+ exclusions can inspect JSON:API writes. `sendPath` accepts per-request headers;
2961
+ typed operations can declare header Schemas. Authentication still owns its
2962
+ headers and applies last. A provider's default call-admission policy yields to
2963
+ an explicit deployment policy. A typed refusal thrown by the probe's reply
2964
+ describer becomes the ordinary failed credential-test result.
2965
+
2966
+ `emptyRequired(schema)` preserves a legacy output object's explicit
2967
+ `required: []` through Effect's canonical JSON Schema rendering. It refuses
2968
+ an object with required properties; its decoder is unchanged. Closed empty
2969
+ input records render `properties: {}` and `required: []` explicitly.
2970
+
2971
+ A vendor may set `manifestBase` to prefix its documented operation paths.
2972
+ The prefix accepts literal path segments and whole `{param}` segments whose
2973
+ names contain only letters, digits and underscores. It is concatenated verbatim
2974
+ for the manifest; transport paths remain relative to the confined base URL,
2975
+ with no parameter interpolation. The catalog renderer
2976
+ collapses unions of bare object and array branches into a type array; branches
2977
+ with additional constraints retain `anyOf`.
2978
+
2979
+ Every answer passes through bounded reading, sent-secret redaction, vendor
2980
+ result handling, reviewed value safety and the deployment privacy projection
2981
+ before paging or provider projections can read it. Read methods beyond GET
2982
+ and HEAD require a review. Construction fixes each write step's capacity;
2983
+ the guarded transport reserves it before preparation and the shared core
2984
+ decides the whole call's write outcome. Dedicated upload builders wait for
2985
+ the Google Drive migration in C.7.
2986
+
2987
+ Reviewed path calls resolve their actual method and path through an indexed
2988
+ safety adapter; a provider-supplied template cannot bypass the policy.
2989
+ Unindexed paths require an explicit global review and no reviewed table;
2990
+ a global review never substitutes for a reviewed table's resolver. A uniform
2991
+ policy over legacy unindexed paths declares its own resolver. Sent-secret redaction
2992
+ covers response headers before result hooks can extract fragments. Withheld
2993
+ hooks admit only operation-bound status failures or unreadable bodies; other
2994
+ failures become defects whose text the API boundary withholds. Hook defects
2995
+ are rewrapped once at the tool or credential-probe edge when text is withheld,
2996
+ including defects from reviewed redaction, vendor error mapping, and the
2997
+ provider program that carry a `ConnectorCallError`. A failing visibility hook
2998
+ also withholds the defect's text; non-withheld operations retain their original
2999
+ defects.
3000
+
3001
+ ### Production HTTP dispatch (contributor reference)
3002
+
3003
+ `src/server.ts` retains the origin refusal, HTTPS upgrade and security wrapper,
3004
+ then calls the private `src/routes/dispatch.ts` adapter. Each fetch handler owns
3005
+ two immutable public Effect HttpRouters: a human table selects CIMD, connect,
3006
+ OAuth callback, metadata, health and MCP programs; the agent table selects only
3007
+ the existing agent endpoint program. The raw agent-namespace guard runs before
3008
+ human dispatch. An unmatched human route runs optional UI fallthrough, wildcard
3009
+ OPTIONS metadata/CORS, or the deployment's final 404. Core selection uses the
3010
+ router directly, without scanning the other core modules. CIMD runs before UI;
3011
+ every other human program runs after optional UI, including its null fallthrough.
3012
+ Metadata retains MCP-before-provider discovery order.
3013
+
3014
+ The synchronous constructor does no routing work or I/O. The first existing
3015
+ edge fiber builds both fixed tables with public `make`, `addAll` and
3016
+ `asHttpEffect`. Only synchronous registration and cache publication use
3017
+ `Scheduler.PreventSchedulerYield` and an uninterruptible region. The cache
3018
+ check is inside that region, so even concurrent first requests with a 16-operation
3019
+ yield budget publish only two routers. Normal scheduling and interruption are
3020
+ restored before any application callback or I/O. Cached route effects capture
3021
+ only deployment programs; they read the private current `RouteContext` service
3022
+ when executed. No request, body, signal, Promise, client, Scope or request
3023
+ services are retained in the tables. The canonical request/call ownership in
3024
+ the MCP program remains unchanged.
3025
+
3026
+ Routing uses a body-free GET projection of the URL, with percent escapes and
3027
+ the matcher's semicolon delimiter protected. The original request remains in
3028
+ `RouteContext`: existing programs own exact raw paths, arbitrary extension
3029
+ methods, JSON 405/Allow, HEAD, private OPTIONS, cookies, authentication and body
3030
+ refusal order. Router normalization cannot grant a canonical path to an encoded
3031
+ prefix, and invalid escapes in an owned suffix still reach the owning program.
3032
+ The projection introduces no second request handler or runner. Web responses
3033
+ pass through `HttpServerResponse.raw`/`toWeb` with their identity and stream
3034
+ ownership intact. The short dispatch Scope does not own MCP's body-held permit
3035
+ or the OAuth callback's uninterruptible commitment. Only a genuine public
3036
+ `RouteNotFound` before handler entry falls through; application failures,
3037
+ including structural lookalikes and genuine router errors, retain their identity.
3038
+
3039
+ Four public `effect/http/*` modules are allowed only in the private dispatcher;
3040
+ all other root-purity and optional-implementation boundaries remain enforced.
3041
+ Direct public module imports avoid the area's barrel cost. The local D3
3042
+ measurement over the reviewed D1/D2 stack is root 373,435 to 393,470 bytes gzip
3043
+ and Worker 758,274 to 774,384 (official Node 22.23.4/zlib 1.3.1-e00f703, identical
3044
+ repository bundle options). The barrel alternative measures 423,348/787,614.
3045
+ The parent accepted this measured native-routing cost with a Worker cap of
3046
+ 785,000 bytes, including room for separately measured native-auth and reviewed
3047
+ host/transport additions. Root and all other caps and historical baselines are
3048
+ unchanged by D3. Separate-input costs are not a combined gzip measurement;
3049
+ the integrated deployment must be measured again.
3050
+
3051
+ `test/server.test.ts` asserts production routing directly on Node and workerd:
3052
+ raw prefixes and malformed suffixes, extension methods, CIMD before auth/UI/body
3053
+ reads, Response/error identity, unmatched fallthrough, concurrent request
3054
+ isolation, and body-held permits through end/failure/cancel/abort/abandonment.
3055
+ `test/node.node.test.ts` bundles one unmodified production Worker and
3056
+ verifies that a queued request owns its native I/O after the preceding response
3057
+ ends. Server-route, request-admission and request-pipeline suites retain ingress
3058
+ ordering, security headers, overload/shutdown and cancellation regressions.
3059
+ Root purity, declaration and bundle checks own import boundaries and size caps.
3060
+ The frozen legacy dispatcher, source transforms, diagnostic latency loops and
3061
+ rejected Effect MCP adapters were migration evidence; their historical source
3062
+ is linked from decision 0016 rather than rebuilt by ordinary tests.
3063
+ The upward MCP protocol leaves remain SDK implementations, with D7's separate
3064
+ native-orchestration work and compatibility evidence unchanged.
3065
+
3066
+ ### Why not Schema or HttpApi for core routes
1485
3067
 
1486
3068
  Both were measured and both lost. `effect` is imported through its barrel,
1487
3069
  which defeats tree-shaking: the first conversion step measured about +20 ms
@@ -1495,12 +3077,17 @@ the operator and activity routes cost about +100 KB gzip on `./ui` and +130 KB
1495
3077
  on `./activity`, and matching the wire format meant opting out of most of what
1496
3078
  it does: unowned paths fall through rather than 404, a wrong method is a JSON
1497
3079
  405, and the content-type and size checks run before a body is read. The root
1498
- entry may import only `effect` itself (`test/purity.node.test.ts`). Effect v4's
1499
- area imports, such as `effect/http-api` and `effect/ai`, would have to stay
1500
- behind a subpath. APIs tagged `@stability unstable` can still change in minor
1501
- releases, which is why the root may import only `effect` itself: that is what
1502
- makes a caret range safe, and it lets a deployment that also uses Effect
1503
- resolve a single copy. Adopting an unstable subpath is its own pull request.
3080
+ entry imports `effect` and the four public HTTP modules confined to the private
3081
+ dispatcher (`test/purity.node.test.ts`).
3082
+ Effect 4.x is a long-term-support line, but its area barrels `effect/http`,
3083
+ `effect/http-api`, `effect/schema`, and `effect/sql` are still tagged
3084
+ `@stability unstable` and can change within it; only core modules such as
3085
+ `effect/Schema` are stable. That is why Effect is pinned to one exact version
3086
+ and every Effect upgrade is its own deliberate pull request. A deployment
3087
+ that also depends on Effect, such as the Worker example's Alchemy stack, must pin the
3088
+ same version to resolve a single copy. Connecta's own lockfile and packed
3089
+ install are checked for one copy (`test/package-surface.node.test.ts` and
3090
+ `scripts/check-package.mjs`).
1504
3091
 
1505
3092
  The stable `4.0.0` release was re-evaluated on 2026-10-01 with
1506
3093
  [`scripts/probes/effect-v4-evaluation.mjs`](https://github.com/zackbart/connecta/blob/main/scripts/probes/effect-v4-evaluation.mjs).
@@ -1526,7 +3113,9 @@ wrong method and an unowned path; our routes need a JSON 405 for the former
1526
3113
  and module fallthrough for the latter. Their `toWebHandler` also builds its
1527
3114
  layer immediately and owns a runner, so adopting it would require proving
1528
3115
  global-scope safety and preserving our scheduler and response-lifetime rules.
1529
- The existing Effect route programs keep those rules without this routing layer.
3116
+ Those historical defaults are not the production adapter: the narrow public
3117
+ HttpRouter dispatch above preserves explicit method refusals, unowned fallthrough
3118
+ and the existing edge runner without `toWebHandler`.
1530
3119
 
1531
3120
  Effect's MCP server has a separate compatibility gate. Its `2026-07-28`
1532
3121
  adapter accepted a stateless `tools/list`, but its `2025-06-18` adapter refused
@@ -1535,15 +3124,20 @@ next request returned 404 on a fresh handler; Connecta served the same fresh
1535
3124
  legacy list with 200 JSON and no session. The stable package exports no MCP
1536
3125
  client, so it also cannot replace our downstream SDK and OAuth implementation.
1537
3126
  [Issue #622](https://github.com/zackbart/connecta/issues/622) records the server
1538
- parity requirements. These measurements support keeping the current MCP,
1539
- validation, and routing implementations; stable v4 alone does not establish a
1540
- replacement's benefit or compatibility.
3127
+ parity requirements. These historical measurements supported retaining MCP protocol and validation
3128
+ implementations. Production HTTP dispatch now uses the separately proven narrow
3129
+ public adapter above; stable v4 alone did not establish its compatibility.
1541
3130
 
1542
3131
  The core's cost is recorded rather than guessed. Against 0.24.4 the root entry
1543
3132
  grew from 235,346 to 279,526 bytes gzip, the Worker example from 263,949 to
1544
3133
  314,336, and each provider by about 36 KB, since each reaches Effect through
1545
- `remoteMcp()`; `pnpm run check:bundle` caps every entry against
1546
- `scripts/bundle-budget.json`. An install carries about 53 MB of `effect`, with
3134
+ `remoteMcp()`. `pnpm run check:bundle` caps the deployed shapes (the root,
3135
+ `./worker`, `./ui`, the operator UI assets, `./d1`, `./auth/access-tokens`,
3136
+ the Worker example, and the other non-provider entries) against
3137
+ `scripts/bundle-budget.json`. Per-provider entries are reported with their
3138
+ size and change from baseline but not capped: every deployment already imports
3139
+ the root, which carries the Effect core, so a provider's standalone bundle
3140
+ counts that core a second time. An install carries about 53 MB of `effect`, with
1547
3141
  no dependencies of its own.
1548
3142
 
1549
3143
  ## Where else to look
@@ -1555,7 +3149,6 @@ src/
1555
3149
  server.ts route ordering, HTTPS upgrade, security wrapper
1556
3150
  routes/ one file per surface; shared.ts holds the auth gate
1557
3151
  skills.ts MCP instructions, the usage skill, connector guides
1558
- catalog-drift.ts reviewed classifications and the drift counts a refresh produces
1559
3152
  provider.ts defineProvider(): the one maintained-provider shape
1560
3153
  activity.ts optional history factory and best-effort recorder
1561
3154
  auth/ Cloudflare Access, clerk (optional peer), downstream OAuth (remote and static)
@@ -1579,9 +3172,423 @@ compiling and configuring the real thing.
1579
3172
  - **Route order is behavior.** Moving a mutation route below the wildcard
1580
3173
  `OPTIONS` opts it into CORS preflight; reordering admission after auth makes
1581
3174
  the cheapest possible attack the most expensive request.
3175
+ - **Operation and call state live in services.** `src/runtime/call.ts` owns
3176
+ the admitted `Caller`, credentials, admission budget, recovery writer,
3177
+ classification, signals, secrets and request ledgers. Registry operations
3178
+ provision caller and operation policy; invocation adds a call lease and
3179
+ finalizes it on every exit. `api()` has a private Effect entry and a shared
3180
+ core with its Promise `callTool`. The public `ConnectorContext` is a view
3181
+ of those services. `deriveContext` shares its entry in the single view
3182
+ bridge; a spread has no caller or admission authority. Candidate credential
3183
+ probes get anonymous read-call services instead of deriving authority.
3184
+ Credentials and charges check closure before and after every await, pending
3185
+ work is interrupted, and late recovery facts are dropped. Every published
3186
+ Promise view and connector operation also races its complete caller-facing
3187
+ Promise against its owner through `settleOnClose`: credential and fetch
3188
+ views, guarded helpers, token minting, catalog loading, MCP operations and
3189
+ applicable authorization operations. An opaque storage, crypto, resolver
3190
+ or body-cleanup wait cannot keep that caller pending after closure.
3191
+ Work starts synchronously after the owner guard so shared-session waiter
3192
+ accounting and claim ownership keep their existing ordering. Late
3193
+ rejections are observed; dispatch and lease guards still contain detached
3194
+ work. Per-await I/O cancellation remains useful for releasing resources,
3195
+ while prompt refusal is enforced at the boundary
3196
+ (`test/promise-boundary-lifetime.test.ts`). Invocation owns
3197
+ the recovery and logical-call records it reads after teardown. Closing an
3198
+ outer call also revokes nested calls paused in preparation. Request-owned
3199
+ MCP sessions and token flights outlive individual calls and close with the
3200
+ request; their credential access comes from the operation. Operation views
3201
+ resolve request cancellation even without an external signal; auxiliary
3202
+ authentication, sends and response handling all use that signal. The
3203
+ invocation deadline and original request cancellation remain separate,
3204
+ so deadline cleanup cannot end request-owned auxiliary work.
3205
+ Concurrent operator probes use separate row scopes, so finalizing a failed
3206
+ row cannot revoke a sibling that is still running.
3207
+ Shared MCP sessions bind renewal preparation to the challenged call's signal,
3208
+ rather than the connection's longer lifetime. OAuth renewal
3209
+ is call-owned until its first durable claim CAS is submitted. Submission
3210
+ transfers preparation, token exchange and fenced rotation commit to the
3211
+ storage coordinator under its own deadlines and deferred-work hook; only
3212
+ the caller's wait still closes with the call. That completion
3213
+ cannot resume a closed call or resend its resource request
3214
+ (`test/call-renewal-lifetime.test.ts`,
3215
+ `test/remote-mcp-renewal-lifetime.test.ts`). Explicit request revocation also
3216
+ aborts pending authorization-code exchange and refuses late token writes
3217
+ (`test/operation-cancellation.test.ts`). Request closure
3218
+ revokes views and calls first, then runs every finalizer in reverse order,
3219
+ collecting failures. The caller's original cancellation signal is retained
3220
+ separately from the lease signal, so normal teardown never looks like caller
3221
+ cancellation (`test/call-services.test.ts`, `test/derived-context.test.ts`).
3222
+ Construction identities and immutable request/failure facts stay in the pure
3223
+ `src/provider-call-facts.ts`, keeping provider stamping and operator sinks
3224
+ free of the Effect call-service graph.
3225
+ - **A write leaves once (INV-9).** `src/write-outcome.ts` owns the shared rules
3226
+ every transport applies to a write: `unknownOutcome` turns a retryable
3227
+ failure that may follow the downstream acting (a 5xx, a gateway's 502 to
3228
+ 504, a lost connection or reply) into a non-retryable `connector_call_failed`,
3229
+ and `unsentAfterUnauthorized` reports a write answered 401 after credential
3230
+ renewal ran, which is never resent: until renewal succeeds it keeps an
3231
+ authorization code (renewal's own verdict, else the 401's), and a renewed
3232
+ credential leaves a non-retryable `connector_call_failed`. Both mark the error
3233
+ `writeOutcome: "unknown"`, which `classifyWriteOutcome` honors before any
3234
+ refusal provenance, so an authorization verdict reached after the write left still
3235
+ counts as unknown in program write accounting (`src/program-writes.ts`),
3236
+ whose result is then that verdict with its handoff, caught or not.
3237
+ `remoteMcp()` classifies by the invocation's verdict
3238
+ (`src/call-classification.ts`), falling back to `classifyTool`, and holds a
3239
+ write to one send per logical call, never per JSON-RPC message: `callTool`
3240
+ puts a dispatch record on the `tools/call` params under a symbol key (spread
3241
+ copies it; `JSON.stringify` drops it), the transport's `send` refuses a
3242
+ second message for that call whatever its id, and the tracked fetch refuses
3243
+ a second request within one message (the SDK's resend after a 401 or a
3244
+ step-up, a redirect hop) and fails a write answered with a redirect. Both
3245
+ layers refuse a missing record unless the active calls for that tool are
3246
+ all reads; catalog hints cannot overrule a served write verdict. Tracking
3247
+ loss gives a non-retryable unknown outcome, never a fresh write attempt. A write
3248
+ always gives the SDK a tool definition, so `callTool` never relists and
3249
+ resends it after a `HeaderMismatch` answer. `api()` OAuth
3250
+ (`src/auth/static-oauth.ts`), the Google Workspace client
3251
+ apply the same one-send rule to credential recovery. All maintained API
3252
+ provider failure mappers run inside `guardedFetch`. Each `api()` entry owns
3253
+ a fresh request ledger (`src/provider-call.ts`) on its derived handler context;
3254
+ further `deriveContext` copies share that ledger without sharing it with
3255
+ concurrent calls. Every guarded request records dispatch, response status,
3256
+ and provider-declared auxiliary status. Plain handler fetches and OAuth
3257
+ resource sends record their dispatch too; OAuth credential checks precede
3258
+ the resource dispatch record. Token exchanges, GitHub installation
3259
+ and repository binding lookups, token minting, and Cloudflare zone-ownership
3260
+ pin lookups are auxiliary: they cannot perform the logical resource write.
3261
+ No transport rejection proves a dispatched write unsent, whatever errno it
3262
+ names. One `fetch` call can hide several attempts: Node's fetch sends a
3263
+ request again after a 421 (even under `redirect: "manual"`) and after an
3264
+ HTTP/2 `REFUSED_STREAM`, both of which servers define as not processed, and
3265
+ an operator-installed dispatcher such as undici's `RetryAgent` can retry
3266
+ further. A refusal on the last attempt says nothing about the earlier ones.
3267
+ Remote MCP writes on Node use the owned runtime sender installed by
3268
+ `listen` from `/node`, selected before recording dispatch. Without it they
3269
+ are refused before sending. Remote MCP reads retain native fetch; Workers uses native
3270
+ fetch with manual redirects. The owned Node sender bypasses the global
3271
+ dispatcher and fetch's 421 replay, preserving the SDK's JSON/SSE bodies and
3272
+ abort signals. A reset or unconfirmed server failure remains unknown.
3273
+ Connecta never reports a transport-failed write as known failed. A write
3274
+ is a known failure only on refusal provenance recorded where the evidence is
3275
+ observed, never inferred from an error code: (a) the ledger proves it never
3276
+ left, or (b) the downstream served an HTTP refusal, a 4xx other than 408
3277
+ (`refusedStatus` and `vendorRefusal` in `src/provider-call.ts`), under the
3278
+ provider's existing served classification (a write's HTTP 409 is a `conflict`). One helper, `servedRefusal` in `src/write-outcome.ts`, decides
3279
+ (b) for the ledger and for a transport outside one: a refusing status the
3280
+ provider reads as `unavailable` or a timeout (Stripe's 424) is not proof, so
3281
+ only a genuine idempotency key keeps such a direct write's retry. Nor is a
3282
+ 401, since a server may act on a request before it rejects the credential,
3283
+ unless the transport declares reviewed `authBeforeExecution` evidence
3284
+ (`GuardedFetchOptions`: the vendor's cited guarantee and a narrow predicate
3285
+ over the raw status and headers and the request's method and path, checked
3286
+ at construction) and the predicate matched, which the ledger records on the
3287
+ request. Only Stripe declares it (`src/providers/stripe/api.ts`, reviewed
3288
+ as a `manual` check in its `drift.json`): an API v1 answer carrying Stripe's
3289
+ `Request-Id`. `test/provider-failure-sweep.test.ts` holds that list to the
3290
+ sources. Q8 is decided in one place, `settleWrite` in
3291
+ `src/write-outcome.ts`, once per logical call and over every request it
3292
+ recorded. The invocation calls it for every failure of a served write: a
3293
+ thrown or mapped error, a custom connector's, a program's call, and its
3294
+ own deadline or the caller's cancellation, when no connector failure
3295
+ arrives at all. It reads the logical call's requests
3296
+ (`logicalCallRequests`), which hold every resource request dispatched
3297
+ under the call from any ledger: nested `api()` calls, remote MCP, guarded
3298
+ transports, `ctx.fetch` and the kit. Without an invocation (a direct call,
3299
+ or a context no invocation opened), only the outermost boundary calls it,
3300
+ at the end of `providerCallFailure`, over its own ledger and every ledger
3301
+ of an `api()` call entered beneath its handler (`nestedProviderCall`,
3302
+ `nestedProviderRequests`), or over the one request a transport outside a
3303
+ ledger sent. Boundaries only record what they observed: the dispatch and
3304
+ status, a reviewed `authBeforeExecution` match, the 401's own mapped verdict
3305
+ (`vendorRefusal`), and a renewal of that one request's own credential
3306
+ (`renewedAfterUnauthorized(request, sent, fresh)`, only for a credential
3307
+ other than the one it sent). The rule: a write request answered 401 that
3308
+ is not a declared refusal standing for the whole call makes the call
3309
+ unknown, never retryable, with no wait and no resend or key-reuse advice;
3310
+ while any such 401 is unrenewed the verdict keeps an authorization code,
3311
+ so invocation keeps the `authorize_connector` handoff and picks its
3312
+ reconcile-first words. Its code and words are the failure's when that is an
3313
+ authorization verdict, else the 401's own recorded verdict, else
3314
+ `auth_required`, with a sentence saying the write was sent before the
3315
+ vendor rejected its credential unless the verdict already accounts for the
3316
+ 401 (a renewal's words). When every such 401 was renewed, the handoff goes
3317
+ and the failure keeps its words, unknown and without a wait. A declared 401
3318
+ stands only as the call's one request, sent by the boundary that decides,
3319
+ whose own refusal is the failure (and, served, known for the logical call).
3320
+ The recorders are the guarded transport (dispatch, status, declaration
3321
+ match, mapped verdict), `ctx.fetch` (dispatch, status), static OAuth
3322
+ (status, verdict, renewal), Google (per-attempt entry, renewal of a read's
3323
+ replay), the kit (core ledger entry, status, `recordUnauthorized`,
3324
+ refresh-once renewal),
3325
+ `remoteMcp()` (each `tools/call` as an entry under its caller's ledger and
3326
+ logical call, status, the verdict, and renewal by comparing the
3327
+ `Authorization` of the resend the SDK attempts), and `api()` itself (one
3328
+ ledger per call, linked to the one whose handler entered it). The same
3329
+ decision settles a call that returned (`settleWriteResult`, before its
3330
+ result is processed in the invocation, and in `providerCallResult` at an
3331
+ outermost direct boundary, including a guarded transport outside any ledger):
3332
+ any undeclared write 401 blocks success, even after renewal. Renewal drops
3333
+ only the handoff; a program still counts the write in `writes.unknown`.
3334
+ A caught read 401 needs positive operation or tool classification, plus a
3335
+ recorded GET or HEAD, to leave success intact; a method alone proves no
3336
+ read. The guarded transport records its operation classification, otherwise
3337
+ its own `api()` tool supplies it (including nested read tools).
3338
+ `ctx.fetch`, OAuth and the kit record the same evidence. The final outgoing
3339
+ `X-HTTP-Method-Override`, `X-HTTP-Method` or `X-Method-Override` header
3340
+ makes the request a write regardless of its classification or method. A
3341
+ declared refusal followed by a successful write also leaves success intact.
3342
+ A custom guarded transport delegating through `ctx.fetch` or `ctx.oauth.fetch` records the wrapper
3343
+ separately from each physical send. Each physical entry records its own
3344
+ outgoing method and response; a later response cannot erase an earlier
3345
+ 401, and a delegated POST cannot inherit a wrapper's GET read evidence.
3346
+ The inner entry does not inherit the wrapper's declaration or explicit
3347
+ read evidence, including when the init is copied. Until linked recording
3348
+ is reviewed, that duplicate may conservatively keep a successful
3349
+ composite unknown even when the wrapper proves its own refusal or read.
3350
+ Request entries are append-only: every physical send, including a read
3351
+ replay after OAuth renewal, gets its own entry. Dispatch refuses an entry
3352
+ already sent and locks its sent method and dispatched flag; the request's
3353
+ own response status is recorded once and locked, and its own renewal can
3354
+ only add a locked mark. These fields are readonly in TypeScript too.
3355
+ The built-in `oauthBearer` transport hands an unsent entry to a known
3356
+ recording OAuth grant once, before any dispatch. The grant consumes that
3357
+ handoff before credential reads and creates a new entry for each further
3358
+ send. It never borrows a dispatched entry. Returned-response identity
3359
+ names the entry whose status was already recorded, without recording it
3360
+ again. This preserves the built-in transport's refusal/read provenance;
3361
+ custom delegation and copied init objects remain conservative.
3362
+ Remote MCP captures
3363
+ each physical read's entry before awaiting its response; a later send
3364
+ cannot overwrite it. The kit's response identity and Google's init identity
3365
+ identify the request whose credential was renewed. The legacy guarded-fetch
3366
+ `answeredRequest` helper has no production caller; tests retain it to exercise
3367
+ renewal provenance outside the kit.
3368
+ The decider normalizes every failure it settles,
3369
+ typed or not (a raw network error, an `AbortError`, a thrown string). Its
3370
+ verdict is final (`settledWrite`, which follows copies and redaction): the
3371
+ invocation reads the mark before anything rebuilds the details, and its
3372
+ enrichers then add no key-reuse, resend or wait advice; only the
3373
+ reconcile-first `retry` words and the handoff. A 408 is a
3374
+ timeout, and a 2xx followed by a mapper, parser, or output-validation
3375
+ failure may have applied. `classifyWriteOutcome` counts a
3376
+ dispatched write `failed` only with that provenance (`answered`, or the
3377
+ error's private known-failure mark); a bare `rate_limited`,
3378
+ `invalid_args`, or `conflict` code is `unknown`. A downstream tool's
3379
+ `isError` is a read's answer only: for a write it arrives over a 2xx after
3380
+ the handler ran, so it is unknown and non-retryable, and its text stays.
3381
+ Refusal evidence holds only for its whole logical call. The invocation
3382
+ opens one per dispatch (`openLogicalCall` in `src/provider-call.ts`), and
3383
+ every `api()` and `remoteMcp()` `callTool` entered beneath it joins it
3384
+ (`enterProviderBoundary`). A served write's mark stands
3385
+ (`knownForLogicalCall`) only when at most one boundary was entered and the
3386
+ invocation invoked that boundary object with its own `callTool`, or a
3387
+ maintained connector object declared to delegate each call to one boundary
3388
+ (`delegatesEachCall`, GitHub) with its declared function. Identities are
3389
+ captured, never read back: the invocation reads the connector and its
3390
+ `callTool` once and invokes that function with `Reflect.apply`, never
3391
+ through the function's own `call` or `apply` property; each boundary
3392
+ records its object with the published function when it is built
3393
+ (`providerBoundary`) and names that object, from its closure, on entry;
3394
+ and a delegator is declared at construction. A connector that reassigns
3395
+ `callTool` mid-call, or a copy (`{ ...boundary }`, `Object.assign`) that
3396
+ shares a boundary's or delegator's function, borrows no standing;
3397
+ `defineProvider`'s inheriting stamp keeps its connector's
3398
+ (`aliasCallIdentity`). A boundary marks a write known only when
3399
+ every request it dispatched is proven unapplied, so the rule reads: known
3400
+ only if every request the logical call dispatched is proven unapplied and
3401
+ the failing request carries the refusal. A custom connector that composes
3402
+ calls, or that could send around them, never inherits an inner verdict; the
3403
+ invocation withdraws the mark and the write is unknown.
3404
+ Write inference also needs positive evidence before relaxing timeout or auth
3405
+ reconciliation. `LogicalCall.auxiliaryReads` records each auxiliary GET or HEAD
3406
+ at dispatch, without method-override headers, separately from the call-owned
3407
+ requests in `sent`. Its physical read proof does not depend on the served
3408
+ tool's write classification. `onlyAuxiliaryRequests` requires exactly one
3409
+ boundary that owns the logical call and declares complete request tracking
3410
+ (`tracksEveryRequest`), at least one dispatched auxiliary resource read, and
3411
+ no dispatched call-owned request or kit auxiliary falsely declared as a read.
3412
+ The kit records `blocksAuxiliaryProof` at dispatch for an `effect: "read"`
3413
+ request without physical GET/HEAD proof; `nonReadAuxiliaryDispatched` retains
3414
+ that blocking fact on the logical call. The
3415
+ invocation's `mayHaveWritten` is true
3416
+ for an entered write unless the call answered with that proof; an explicit
3417
+ `writeOutcome: "unknown"` always overrides the relaxation. Empty or partial
3418
+ ledgers, token POSTs alone, multiple boundaries, custom `api()` and
3419
+ `remoteMcp()` connectors, deadlines, and caller cancellation supply no such
3420
+ proof. Only maintained providers have the internal tracking declaration;
3421
+ they must route every dispatch through their tracked transports. The kit
3422
+ still classifies auxiliary POSTs and method-overridden GETs as writes for
3423
+ reservation and replay; the effect label does not change those rules.
3424
+ Their dispatches block auxiliary-only inference even beside a genuine
3425
+ auxiliary read, and count as call-owned requests for write-failure settlement,
3426
+ including a 503 or a mapped `unavailable` verdict. Guarded authentication
3427
+ token POSTs supply no read proof,
3428
+ but do not invalidate a later auxiliary resource read. A conservative
3429
+ timeout withdraws `answered`; a conservative auth failure does so only with
3430
+ an empty resource ledger, also withdrawing that ledger's private known-failure
3431
+ mark before advice is composed. A refused call-owned write keeps its
3432
+ `answered` evidence and resource-refusal provenance only with a complete
3433
+ ledger proving the refusal covers the whole call; its auth recovery advice
3434
+ uses the same known outcome as program accounting. Partial or unknown ledgers
3435
+ remain conservative, as do token-mint and auxiliary auth failures without
3436
+ positive resource-read proof. Auth failures with no
3437
+ call-owned dispatch, including credential refusals and static-OAuth refresh
3438
+ failures, keep conservative program accounting and reconciliation wording.
3439
+ Pre-dispatch refusals mark themselves: `api()`'s unknown tool and argument
3440
+ validation, `remoteMcp()`'s allowlist and non-auth failures before its
3441
+ `tools/call` is sent, and GitHub's REST target check and token mint. Auth
3442
+ failures after entering a write with an empty resource ledger remain unknown,
3443
+ including a `remoteMcp()` connect or initialize 401 before `tools/call`.
3444
+ `remoteMcp()` applies the rule to direct and served writes alike: once
3445
+ `dispatch.sent`, a JSON-RPC error (`-32602` included), an unreadable result,
3446
+ or a failed output validation is unknown; only an HTTP 4xx (not 408) on the
3447
+ request is a refusal. A downstream's `input_required` is no proof either: a
3448
+ handler asks for input from inside its run, so a write's unsupported
3449
+ suspension (`src/invocation.ts`) and a direct write's failed relay
3450
+ (`src/downstream-input.ts`) are unknown and say to reconcile, not resend.
3451
+ The invocation finalizes every served write `classifyWriteOutcome` calls
3452
+ unknown before it adds advice: `retryable: false`, `writeOutcome:
3453
+ "unknown"`, and no grant-then-retry or correct-and-retry text, so a custom
3454
+ connector's code cannot advertise a retry. Advice to send again (a rate
3455
+ limit's "back off before retrying", a token or OAuth preflight's "then
3456
+ retry authorization") is a segment of its own, attached with
3457
+ `resendAdvice` in `src/errors.ts` where it is written and never part of
3458
+ the error's message. Output composes it only once the outcome allows it:
3459
+ `classifyCallError` for a direct caller, unless the write is already
3460
+ unknown, and the invocation after its verdict (it classifies with
3461
+ `classifyWithoutAdvice`). So no redaction of a private argument, no
3462
+ wrapper's rewording, and no preflight failure after an earlier dispatch
3463
+ can leave it on an unknown write, while a read and a known refusal keep it.
3464
+ Key-reuse advice ("reuse this key", "cannot repeat the write") needs more:
3465
+ proof that its key protects the whole logical call
3466
+ (`keyProtectsLogicalCall`). Every resource request dispatched under the
3467
+ logical call, from any ledger, is recorded on it once per dispatch with
3468
+ its method and the genuine key it carried (`ProviderRequest.key`, set by
3469
+ `callRest` only for a key fetch sends as given). The advice stands only when
3470
+ exactly one boundary was entered, the invocation's object owns it as for
3471
+ `knownForLogicalCall`, that boundary tracks every request, and exactly one
3472
+ dispatched request was a write, carrying the named key; a request counts as
3473
+ a read only with positive operation/tool classification and a recorded
3474
+ GET or HEAD (`readRequest`); method-override headers make it a write. A composite, a
3475
+ wrapper, a second write, an earlier unrelated key, or a call interrupted
3476
+ after two dispatches gets no such advice; the key stays a diagnostic, in
3477
+ the message and `uncertainCall.recovery`. The REST write tool's
3478
+ generated-key advice is a segment of its own (`keyResendAdvice`), which the
3479
+ invocation composes only under that proof and `classifyCallError` composes
3480
+ for a direct caller of the one boundary that sent the key.
3481
+ An authorization or credential handoff on an
3482
+ unknown write says to re-read the target before any fresh call instead of
3483
+ "retry the original call" (`src/authorization-handoff.ts`). The guarded
3484
+ `HttpClient` owns transport attempts through `src/guarded-transport.ts`;
3485
+ Node uses the owned sender in `src/http/node-transport.ts`, installed by
3486
+ `/node` when `listen()` starts serving. Reads keep their retry advice. Every maintained
3487
+ provider declares (`tracksEveryRequest`) that its handlers send only through
3488
+ channels the ledger records; for an undeclared `api()` handler an empty
3489
+ ledger is no evidence, so its served write fails unknown once the handler
3490
+ has run. When the handler settles, `api()` closes the ledger before any
3491
+ decision: a resource request still waiting to leave (a sibling reading its
3492
+ credential) is refused before its credential read and again before send,
3493
+ and one in flight already counts as dispatched.
3494
+ `providerCallFailure` in `src/write-outcome.ts` finalizes the call once at the
3495
+ `api()` boundary, including handler-thrown typed errors. For a served write,
3496
+ no resource dispatch keeps the failure's own advice. A vendor's own 4xx
3497
+ refusal (a 4xx other than 408) is a known failure only when no other
3498
+ resource request was dispatched. Refusal provenance travels with the error independently of its
3499
+ code or wording, so a generic `connector_call_failed` refusal still counts
3500
+ as failed in program write accounting. Every other possibly executing
3501
+ failure is non-retryable with `writeOutcome: "unknown"`; authorization codes
3502
+ keep their handoff. A write answered 401 is one of these unless its
3503
+ transport declares `authBeforeExecution` evidence that matched, and it
3504
+ keeps an authorization code and handoff whatever failure surfaced after
3505
+ it, with no wait.
3506
+ A later refusal cannot make an earlier successful request safe to repeat.
3507
+ Provider recovery wrappers change wording through helpers that preserve
3508
+ verdicts and provenance; Google also retains its response phases and reasons.
3509
+ No operation hint (a whole-resource `PUT`, fixed cells, a content address)
3510
+ grants retry credit, direct or served. A served write requires
3511
+ reconciliation even with an idempotency key; direct transport calls
3512
+ without a served verdict can still prove repeatability, but only with a
3513
+ genuine key sent to a vendor that honors it (Stripe today, through
3514
+ `idempotencyHeader`): the REST write tool refuses an `idempotencyKey` that fetch
3515
+ would send empty after trimming or would not send at all (CR, LF, NUL, any
3516
+ other control character but tab, DEL, or a code point past Latin-1), as
3517
+ `invalid_args` before recovery is recorded, and `callRest` marks a request
3518
+ repeatable only for a key fetch sends as given.
3519
+ `providerCallFailure` (`src/write-outcome.ts`) is the one finalizer for a
3520
+ direct write's failure: the `api()` handler boundary, a guarded transport
3521
+ outside a call ledger, and every `remoteMcp().callTool()` write, direct or
3522
+ served (with its `tools/call` dispatch record as the ledger and the
3523
+ endpoint's HTTP status as the served verdict) all hand it every write
3524
+ failure. Guarded transports
3525
+ inside a call (`ctx.fetch`, the shared REST layer, Google) defer to the
3526
+ `api()` boundary. No identity passthrough or code filter runs ahead of it
3527
+ for a direct write. It recognizes every timeout and abort before any code
3528
+ filter: the caller's own signal (an `AbortSignal.timeout` included, and
3529
+ however the runtime's fetch reports it), an SDK or connecta deadline
3530
+ (`code: "timeout"`, an HTTP 408), and a timeout errno. Without a genuine
3531
+ key, such a write is unknown and non-retryable: a timeout keeps its code
3532
+ and `details.code`; the caller's abort becomes `timeout` or
3533
+ `connector_call_failed` with its reason as `cause` (a redacted copy of the
3534
+ same class where output redaction applies, as on `remoteMcp()`) and its
3535
+ class and code in `details`. No invocation exists to report it as
3536
+ `write_outcome_unknown`. The caller's own abort reason still passes by
3537
+ identity for a read and for a served call, whose timeout passes through
3538
+ unchanged for the invocation to report.
3539
+ The catalog-driven `test/provider-failure-sweep.test.ts` checks every API
3540
+ provider/tool on Node, fails on missing provider/tool examples or a
3541
+ dispatched failure bypassing the helper, and covers served write/read
3542
+ overrides with HTTP 503 for every tool and a connection reset after send
3543
+ for each provider/verdict representative. HTTP 401 recovery uses those
3544
+ representatives plus the distinct composite, auxiliary, owner-lookup and
3545
+ POST-as-read paths; every provider's full golden failure matrix still runs.
3546
+ Shared sender and real-socket evidence below own status/reset permutations.
3547
+ Local metadata
3548
+ tools prove zero sends; composite calls prove each distinct request leaves
3549
+ once. The round-5 cases additionally cover Docs creation with initial text,
3550
+ composite success followed by refusal, GitHub preflight failures, typed
3551
+ handler errors after successful sends, Forms cursor conflicts after 200,
3552
+ generic-code vendor refusals, auxiliary failures after resource dispatch,
3553
+ redaction of refusal errors, and concurrent direct-call ledger isolation.
3554
+ The round-6 cases cover a composite write's early refusal while siblings
3555
+ await credentials or are in flight, requests started after the call ended,
3556
+ connect-phase errnos with each body kind (all unknown for a write, and a
3557
+ direct read stays retryable), a request aborted with a refusal-shaped
3558
+ reason, an errno outside fetch's network error, and
3559
+ undeclared handlers whose untracked send precedes a typed failure. Each
3560
+ catalog case also checks that the ledger recorded every resource request
3561
+ the network saw, and the inventory that every API connector declares it.
3562
+ `test/write-evidence.test.ts` uses real refused, reset, and held sockets on
3563
+ both runtimes: a refused write is unknown and a refused read retryable,
3564
+ including a write aborted with another fetch's real refusal as its reason
3565
+ after the server read its body; `test/write-evidence.node.test.ts` covers a
3566
+ Planning Center POST that undici resends after a 421 to a closed listener, a
3567
+ DELETE that a `RetryAgent` resends after a reset, and a streamed body and a
3568
+ `Blob` with an overridden `stream()` that undici partly sends before its
3569
+ producer fails. Each has the server holding the write and reports unknown.
3570
+ Additional tests:
3571
+ `test/remote-mcp-write-once.test.ts` (real SDK transport),
3572
+ `test/api-oauth.test.ts`, `test/google-workspace-delegation.test.ts`, and each
3573
+ provider's `INV-9` tests.
3574
+ - **Plain `ctx.fetch` is not confined to an origin, by design.** In an `api()`
3575
+ handler it is the global `fetch` plus sent-secret registration. It carries no
3576
+ grant: only `ctx.oauth.fetch` attaches the OAuth token, and only to
3577
+ `apiOrigins`, and every maintained provider sends through a guarded transport
3578
+ confined to its base URL. A custom handler is deployment code, which owns its
3579
+ destinations (goal 4); no agent program reaches `ctx.fetch`, so INV-3 holds
3580
+ below the sandbox, and a credential a handler sends is redacted from what the
3581
+ agent sees (INV-5). Confining it would need a declared origin list on every
3582
+ `api()` connector and would break handlers that follow a URL their API
3583
+ returned, such as a signed download. `test/api-oauth.test.ts` pins that the
3584
+ plain fetch carries no grant.
1582
3585
  - **`close()` is idempotent and ordered.** Both admission pools, then the
1583
3586
  connector limiters, then the executor; Node's `listen()` calls it on
1584
- SIGTERM/SIGINT.
3587
+ SIGTERM/SIGINT. The listener stops accepting connections and closes idle
3588
+ keep-alive sockets plus accepted sockets that have not produced their first
3589
+ HTTP request, then waits for active responses, deferred work, and executor
3590
+ cleanup. `test/node.node.test.ts` covers shutdown after token adoption,
3591
+ rotation, and doctor with an unused connection and a live QuickJS execution.
1585
3592
  - **Structural mistakes throw at construction.** A duplicate connector id, an
1586
3593
  invalid admission rule or limit, an unknown option at any depth, the old
1587
3594
  boolean `accessTokens` option, malformed or repeated `agents` or agents
@@ -1601,3 +3608,317 @@ The contract now lives in [Operator UI](./operator-ui.md#operator-data-contract)
1601
3608
  including configuration provenance, live facts, activity disclosure, doctor,
1602
3609
  and the browser implementation. [Optional deployment modules](#optional-deployment-modules)
1603
3610
  keeps the module composition and request-lifetime rules here.
3611
+
3612
+ Vendor error declarations may review structural text presence at fixed body
3613
+ paths. The error boundary exposes only `present` and `nonBlank`, after
3614
+ sent-secret redaction, and never the text. Reviewed recovery-token lists can
3615
+ set a fixed `displayFacts` bound with a reason; other lists display eight
3616
+ entries. Both reviews are snapshotted at connection construction and never
3617
+ change classification or the operation's error-withholding policy.
3618
+
3619
+ Reviewed numeric error-body paths expose finite, nonnegative numbers only.
3620
+ Nonblank numeric strings may be converted; empty or blank strings are absent.
3621
+ The original text never reaches classification. These paths are also captured
3622
+ at construction and cannot change error-withholding policy.
3623
+
3624
+ Native pools resolve names through a shared claimant index for listing and
3625
+ calling, then use the existing invocation routes. Native recovery adapts only
3626
+ Connecta error envelopes in `src/native-recovery.ts`; provider dispatch and
3627
+ continuation enforcement stay in their existing owners.
3628
+
3629
+ ### Contributor reference: owned Node transport dispatch
3630
+
3631
+ `src/guarded-transport.ts` is the platform-neutral runtime slot. `sendGuarded`
3632
+ invokes a dispatch callback before legacy/fake sends; a transport declaring
3633
+ `delaysDispatch` must invoke it exactly once at its last safe pre-send boundary.
3634
+ The Node sender does this before a direct send or after CONNECT/TLS establishment,
3635
+ before exposing the socket to undici's destination Client. A refusal there must
3636
+ never reach the destination or acquire dispatch provenance. Each send owns its
3637
+ Client/ProxyAgent and response lifetime, with HTTP/1.1, non-idempotent dispatch,
3638
+ no interceptors and no redirects. No dispatcher, response, callback or signal is
3639
+ cached across requests. This seam is transport policy; SDK protocol, auth and
3640
+ request/coordinator ownership remain with their existing adapters.
3641
+
3642
+ ### Contributor reference: OAuth credential transport seam
3643
+
3644
+ `sendCredentialPost` in `src/guarded-transport.ts` is a Promise platform leaf for
3645
+ SDK-authenticated token, registration and revocation POSTs. It resolves the
3646
+ owned policy, forces manual redirects and withholds native exception text/cause,
3647
+ while preserving the caller's original abort reason. The SDK still constructs
3648
+ auth headers/forms and decodes the byte-safe response; this leaf supplies no
3649
+ OAuth retry, grant, response-decoding or ownership orchestration.
3650
+
3651
+ `OAuthRefreshCoordinator.coordinatedFetch` accepts an optional final synchronous
3652
+ `prepareCredentialRequest` hook. Production adapters resolve `guardedTransport(true)`
3653
+ there before claiming a code/refresh grant, so an unsupported Node host consumes
3654
+ neither. The base-fetch leaves in static OAuth and remote MCP call
3655
+ `sendCredentialPost`; remote registration and disconnect revocation do so too.
3656
+ D4 must preserve this preflight and the distinction between request-owned code
3657
+ exchange and coordinator-owned refresh after claim submission. The existing
3658
+ spent-token algorithm intentionally remains conservative after a submitted claim,
3659
+ even if a later CONNECT fails before destination dispatch. D6 must preserve the
3660
+ SDK JSON-RPC envelope distinction at the leaf: protocol POSTs are not credential
3661
+ POSTs; forms and registration JSON are. Discovery GETs remain native fetch.
3662
+
3663
+ ### Contributor reference: Node response drain
3664
+
3665
+ The Node listener counts active ServerResponses per socket. Once shutdown starts,
3666
+ each finish/close removes its response; the last completion calls `destroySoon`
3667
+ to flush queued bytes and close the connection. This also covers SSE whose
3668
+ headers were sent before shutdown and pipelined responses waiting behind an
3669
+ inflight response. Normal keepalive remains Node-owned until shutdown. Idle and
3670
+ never-admitted sockets retain their separate close paths, and the configured
3671
+ shutdown deadline still forces exit 1 for work that never completes.
3672
+
3673
+ ### Native auth programs (contributor)
3674
+
3675
+ Inbound verification, shared authentication admission, machine-token verification,
3676
+ static OAuth and the SDK-facing downstream OAuth provider compose private native
3677
+ programs. `auth/native.ts` selects registered implementations by the current
3678
+ function identity; an original Promise shell enters its own implementation
3679
+ without re-reading an overridden method. Class registrations retain their
3680
+ original method and prototype/key separately, so instance, inherited and
3681
+ prototype decorators can delegate to a saved original without recursion. Custom
3682
+ adapters and overridden methods retain their Promise boundary and original errors.
3683
+
3684
+ `auth/native-call.ts` provides the canonical `RequestScope` Context from the
3685
+ existing call view. Native grant preparation and storage reads execute inside
3686
+ that Context and refuse a closed owner; no second request-identity table is
3687
+ introduced. Keeping that call-only module separate also keeps downstream call
3688
+ services out of metadata and operator UI imports. Native storage uses D2's
3689
+ registered storage programs; custom stores remain one-operation Promise leaves.
3690
+
3691
+ `authFlow` composes yielded Effects while delivering original typed failures to
3692
+ local auth policy catches. Interruption remains interruption, runs yielded
3693
+ cleanup and never becomes a diagnostic Cause. A fetch's settlement observer
3694
+ cancels a late body. The resource sender records only retained status/refusal
3695
+ facts after cancellation; it cannot renew a call capability or send another
3696
+ request. Synchronous abort observers finish recording those facts before native
3697
+ wait cleanup removes them.
3698
+
3699
+ Admission charges track the actual settlement of SDK, crypto, fetch and custom
3700
+ Promise leaves. Submitted native storage work has independent settlement so a
3701
+ caller-facing deadline cannot release its charge early. Isolate-wide admission
3702
+ state remains scalar charges, counters and weak reachability; no request-owned
3703
+ client, signal, body or Promise is cached across requests. Organization cache
3704
+ TTLs, lookup ordering and rate-limit backoff remain unchanged.
3705
+
3706
+ Admission composes explicit native continuations rather than suspending a
3707
+ generator that contains its charge token. The workerd collection regression
3708
+ holds live middleware work charged, ends a different invocation, then proves
3709
+ its unreachable charges are collectable. Neither deadline nor abandonment
3710
+ releases a charge; only actual settlement or proven unreachability does.
3711
+
3712
+ Refresh claims still transfer ownership synchronously with claim-CAS submission,
3713
+ before its answer. The bounded native coordinator starts without request/call
3714
+ Context authority and may persist a rotation after its caller closes. A closed
3715
+ caller cannot consume that result or resume sending. The cross-request refresh
3716
+ join deliberately crosses a Promise boundary so workerd resumes each waiter in
3717
+ its own request. Code exchange remains request-owned and one-use. Epochs,
3718
+ permanent spent fingerprints, physical-attempt guards and pre-claim transport
3719
+ preflight are unchanged.
3720
+
3721
+ `authStorage(storage, key, args, beforeSubmit?)` admits the ownership hook and
3722
+ native/custom storage invocation in one JavaScript reaction. Its public
3723
+ Scheduler delegation suppresses automatic yield only during that initial
3724
+ reaction; a microtask restores parent scheduling before pending I/O resumes.
3725
+ The four hook sites are `updateGrant`'s guarded write and `claimRefresh`'s
3726
+ unknown-lease refusal, ambiguous spent-token write and new claim. A forced
3727
+ scheduler regression covers both native and custom stores; real pending TCP
3728
+ I/O, pre-submission cancellation, synchronous failure and unknown CAS outcomes
3729
+ retain their distinct ownership and exactly-once settlement rules.
3730
+
3731
+ Intentional leaves are the Clerk and MCP SDKs (including their callback methods),
3732
+ platform fetch/body readers and Web Crypto, custom identity/gate/sealer/storage
3733
+ hooks, and the reset-settlement observer required by the callback route. Native
3734
+ programs compose around those leaves directly. Public Promise APIs, SDK method
3735
+ signatures and published private class declarations remain unchanged; only
3736
+ `runtime/run.ts` starts fibers. SDK telemetry remains disabled, and raw SDK,
3737
+ body, storage or Cause diagnostics never reach operator records.
3738
+
3739
+ The declaration comparison retains 86 of 88 reachable files byte for byte.
3740
+ The two exact exceptions swap only adjacent `title` / `instructions` members
3741
+ in the inferred provider and Google Workspace schema declarations; both keep
3742
+ the same `Field<unknown, unknown, true>` type. Both owning source files are
3743
+ unchanged. A narrow machine check restores only those two exact adjacent
3744
+ pairs and requires byte equality, with no other type, export or reference
3745
+ change. All 88 declarations still exclude Effect and have complete references.
3746
+
3747
+ The superseding 2026-10-25 D3/D4 cost ruling authorizes only Worker
3748
+ `maxGzip: 785000` (previously 760000) and Clerk `maxGzip: 85000` (previously
3749
+ 63087). Official cached Node 22.23.4 / zlib 1.3.1-e00f703 measured Clerk at
3750
+ 47,688 -> 80,961 bytes gzip (+33,273): the generated SDK is unchanged, while
3751
+ native Effect adds 91.8 KB raw plus the native helper and small auth-source
3752
+ growth. The isolated D4 candidate measured Worker at 762,666, root at 377,177
3753
+ and D1 at 88,321 bytes gzip. Optional-graph optimization brought UI to 342,955,
3754
+ so the earlier unused UI allowance is revoked and its original 345000 cap
3755
+ remains. Historical baselines, root/D1 and all other caps, dependency pins and
3756
+ security/API gates remain unchanged. These independent measurements do not
3757
+ predict a combined gzip by addition; final artifacts must be rebuilt. Native
3758
+ registration, decorated fallback and request/native-storage semantics also
3759
+ require a smoke through an actual minified production bundle. Final post-hook
3760
+ official artifacts measure root 377,290, D1 88,321, Clerk 80,959, UI 342,959
3761
+ and Worker 762,774 bytes gzip, all within those caps.
3762
+
3763
+ ### Native remote MCP programs (contributor)
3764
+
3765
+ Remote MCP connection joins, credential and epoch checks, catalog intake,
3766
+ negotiation, calls, resources, Skills, auth and status compose native programs in
3767
+ `connectors/remote-mcp.ts`. `catalog-cache.ts` owns native SDK cache reads,
3768
+ chunked publication, generation fencing, complete refresh acceptance and listing
3769
+ serialization; `connectors/negotiation-cache.ts` owns native negotiation storage.
3770
+ The published connector, cache and SDK callback methods remain Promise shells.
3771
+ Native consumers select those programs through `authCall` / `authInvoke` by
3772
+ current function identity. Each original shell enters its own immutable program,
3773
+ so a decorator or inherited delegate can call a saved original without recursion.
3774
+ `invocation-auth.ts` follows that same identity dispatch for local credential
3775
+ checks. The remote call registers its final original function with
3776
+ `runtime/call-entry.ts`, preserving invocation validation, policy and call services.
3777
+ A replaced function cannot inherit that native call proof.
3778
+
3779
+ Connector and cache state use the canonical `RequestScope` from the existing
3780
+ view services. The shared connection provides only that request owner, never the
3781
+ first waiter's call services, credentials or budget. SDK cache callback edges
3782
+ restore that same owner. Every operation captures its own generation, credential
3783
+ identity and listing binding before waiting; a later listing cannot adopt an
3784
+ abandoned publication. Request closure revokes the connector and cache scopes.
3785
+ No client, transport, response, signal, promise or live request Context is shared
3786
+ across inbound requests. D4's bounded refresh coordinator retains its separate
3787
+ ownership, synchronous submission gate and waiting-request handoff.
3788
+
3789
+ Two deliberate Promise waits remain: a connect waiter receives the Deferred's
3790
+ answer in its own workerd request continuation, and a serialized listing waits
3791
+ for a Promise turn before running its native program. Cancelling a queued
3792
+ listing releases its turn only after its predecessor, so it cannot unlock a
3793
+ listing still using the SDK cache binding. Cancellation after entry ends that
3794
+ listing's publication without starving a live sibling. Teardown starts native
3795
+ finalizers before checking caller ownership and joins their bounded independent
3796
+ settlement. Only `runtime/run.ts` starts fibers.
3797
+
3798
+ Physical sender Promise observers retain response status, write refusal and
3799
+ redirect facts even when a native waiter closes first. The call's abort path
3800
+ yields one scheduler reaction before settling those facts, including when the
3801
+ SDK rejects synchronously from the abort event. These observations grant no live
3802
+ authority: a closed caller cannot renew, send, publish a cache result or consume
3803
+ late output. Source/effective write classification, one physical attempt,
3804
+ withheld transport text, explicit downstream tool errors, input suspension,
3805
+ final output validation and original abort/error identity remain enforced.
3806
+
3807
+ Intentional leaves are the pinned MCP SDK's actual client/transport operations
3808
+ and schema validators, the owned one-attempt sender and platform fetch,
3809
+ Web Crypto fingerprints, bounded byte/SSE stream readers and body cancellation,
3810
+ and unregistered custom token, credential, storage, sealer or observation hooks.
3811
+ SDK `ResponseCacheStore` is the callback contract, not a replacement client or
3812
+ protocol implementation. Its storage and refresh algorithms are native programs;
3813
+ its Promise shells are required SDK edges. The connector keeps modern/legacy
3814
+ negotiation, header mirroring, cache partitions and TTL semantics. No SDK/Effect
3815
+ pin or supported protocol surface changes.
3816
+
3817
+ Native storage and activity selection in `runtime/native-storage.ts` checks the
3818
+ current methods against their original registered identity, and selected programs
3819
+ check again when executed. A replacement or decorator owns the Promise fallback;
3820
+ restoring originals restores native composition. Registry storage namespaces
3821
+ select the underlying current methods at dispatch, including namespaces created
3822
+ while a source method is decorated. Original SQL shells call their own native
3823
+ program directly. Tests cover current and previously selected programs, own and
3824
+ inherited saved-original delegates, get/set/CAS and activity record/list,
3825
+ restoration, typed failures and interruption. The D4 native proof fixtures install
3826
+ their intentional throwing Promise shells before registration, so they establish
3827
+ native composition without authorizing bypass of later replacements.
3828
+
3829
+ `remote-mcp-native.test.ts` runs native SDK/cache/request-boundary cases on Node
3830
+ and workerd. `remote-mcp-native-bundle.node.test.ts` additionally loads an actual
3831
+ minified production bundle to verify call/cache registration, request Context,
3832
+ native storage, saved-original delegates, custom replacements and closed-owner
3833
+ refusal. Existing wire, physical-attempt, cache and OAuth lifetime suites remain
3834
+ the compatibility evidence; provider golden observations remain unchanged.
3835
+
3836
+ ### Native upward MCP orchestration (contributor)
3837
+
3838
+ `routes/mcp.ts` composes server preparation, native catalog selection, exchange,
3839
+ output redaction and response-owned request cleanup as native programs. The
3840
+ pinned SDK's modern `createMcpHandler` and a fresh stateless legacy JSON transport
3841
+ remain the protocol implementations. Effect 4.0.5's public MCP adapters cannot
3842
+ serve the required fresh legacy contract; this implementation does not adopt
3843
+ Effect McpServer. Each inbound request constructs fresh servers and keeps its
3844
+ admission scope until its response body ends, fails, is cancelled or reaches its
3845
+ existing deadline. Authentication and endpoint admission precede body dispatch.
3846
+ The declared-agent observation stays after successful endpoint admission and
3847
+ before listen classification or body dispatch.
3848
+
3849
+ Meta tools, native tools, Skills/resources and upward auth/input continuations
3850
+ compose registered native programs. SDK registration signatures and Promise
3851
+ shells remain unchanged. Current-method identity governs consumer dispatch;
3852
+ each original shell enters its own immutable program, so decorators, inherited
3853
+ delegates and replacements retain authority. Catalog policy failures reach
3854
+ native consumers as ordinary typed failures. Invocation still owns validation,
3855
+ classification, read-only enforcement, dispatch, write settlement and accounting.
3856
+ Its input and result callbacks select native programs while custom synchronous
3857
+ or Promise callbacks retain their original behavior.
3858
+
3859
+ The canonical `RequestScope` travels through SDK callback edges and registry
3860
+ views. A continuation's durable nonce claim still precedes dispatch, retains its
3861
+ caller, endpoint, arguments, TTL and round bindings, and cannot authorize another
3862
+ write after an ambiguous result. Retained classification and settlement facts
3863
+ never reopen a closed request. A finished program can still shape its response
3864
+ and retained recovery facts after its connector authority closes; the inbound
3865
+ request signal still cancels that response work. Neither path grants another
3866
+ dispatch. Agent-output redaction, withheld diagnostics,
3867
+ secret registration and result bounds retain their existing pure algorithms.
3868
+ `upwardStep` is only a typed generator adapter: it lets `authFlow` deliver the
3869
+ original ordinary failure to a local catch without converting interruption.
3870
+
3871
+ Concurrent authorization failures share one request-owned producer and Deferred
3872
+ answer per connector, including failures and the unknown-write advice variant.
3873
+ The existing `forkRequest` seam starts that producer under the request Scope;
3874
+ its Context contains only the canonical request owner and scheduler. A waiting
3875
+ call's budget, credentials and cancellation do not own the producer. Closing the
3876
+ request revokes it, and a closed request cannot consume or restart its handoff.
3877
+ Only `runtime/run.ts` starts fibers. Admission and downstream refresh/connection
3878
+ waits retain their deliberate Promise handoffs for workerd's waiting-request I/O
3879
+ context.
3880
+
3881
+ Intentional leaves are actual pinned SDK handler/transport/server operations,
3882
+ SDK schema and request-state codecs, platform body readers and cancellation,
3883
+ Web Crypto hashes, vault owner/seal/open/sign/verify methods and unregistered custom
3884
+ identity, credential, storage, executor and tool callbacks. Connecta's OAuth-link
3885
+ binding, TTL, progress, closing and one-use claims are native orchestration around
3886
+ those leaves. The supported protocol and unsupported-feature refusals remain
3887
+ unchanged. Native consumer, failure, decorator, sharing, close and minified-bundle
3888
+ regressions live in `upward-native.test.ts` and
3889
+ `upward-native-bundle.node.test.ts`; existing wire, continuation, redaction,
3890
+ physical-attempt and body/admission suites remain compatibility evidence.
3891
+
3892
+ The combined native/operator graph has a measured contributor bundle allowance
3893
+ in PR #1060: the UI gzip cap is 346,000 bytes, with its historical baseline
3894
+ unchanged. The official full checker measured 345,228 bytes after regenerating
3895
+ the merged UI assets. Removing unused Invocation/Catalog registration retention
3896
+ returned the public Worker entry from 198,744 to 77,426 bytes; live minified
3897
+ Context, inherited-method, decorator and restoration proofs retain native
3898
+ composition. Root, example Worker, public Worker, D1 and Clerk caps remain
3899
+ unchanged. This cost decision grants no later request-body-limit allowance.
3900
+
3901
+ ### Catalog operation vocabulary (contributor)
3902
+
3903
+ The standard hatch privately registers bounded vendor operation text against its
3904
+ ApiTool identity; `apiConnector` transfers those facts to the constructed
3905
+ connector. `operation-vocabulary.ts` contains only pure lexical/cache logic and
3906
+ types. Catalog discovery consumes those facts after loading and classifying a
3907
+ complete visible catalog. It never imports a provider or an operation index,
3908
+ parses operation contracts, invokes a handler, or authenticates. Matching requires
3909
+ all terms in a single operation and a visible destination hatch. Endpoint,
3910
+ connector and tool grants are checked on every query, outside the request-free
3911
+ cache. Exact authored names and complete authored matches precede API pointers;
3912
+ ordinary result limits, offsets, catalog failure disclosure and byte bounds apply.
3913
+ The published discovery contract states compilation, query and cache bounds.
3914
+ No request identity, signal, credential or promise enters a vocabulary cache.
3915
+
3916
+ ### Authenticated body-bound cost (contributor)
3917
+
3918
+ The separate #860 change has a measured allowance only for the example Worker
3919
+ gzip cap, from 785,000 to 786,000 bytes, with its historical baseline unchanged.
3920
+ The official full checker measures 785,036 bytes against D7's exact 784,948-byte
3921
+ lower graph, an actual 88-byte increase. It reuses the bounded platform body
3922
+ inspector, preserves native orchestration and avoids a new reader lifecycle.
3923
+ Root, UI, public Worker, D1, Clerk and asset caps remain fixed. The cost ruling
3924
+ does not approve protocol, cancellation or authority semantics.