@zackbart/connecta 0.34.1 → 0.36.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 (441) hide show
  1. package/CHANGELOG.md +418 -0
  2. package/README.md +65 -105
  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 +2044 -1161
  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 +751 -281
  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 +29 -7
  53. package/dist/connectors/api-connector.js +145 -57
  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 +227 -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 +379 -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/contract.d.ts +8 -3
  109. package/dist/operator-ui/generated.js +4 -4
  110. package/dist/operator-ui/model.d.ts +2 -3
  111. package/dist/program-writes.js +47 -26
  112. package/dist/provider-call-facts.d.ts +115 -0
  113. package/dist/provider-call-facts.js +182 -0
  114. package/dist/provider-call.d.ts +112 -0
  115. package/dist/provider-call.js +271 -0
  116. package/dist/provider.d.ts +65 -31
  117. package/dist/provider.js +116 -39
  118. package/dist/providers/_shared/google/hatch.js +91 -0
  119. package/dist/providers/_shared/google/kit.js +427 -0
  120. package/dist/providers/_shared/google/result-size.js +15 -2
  121. package/dist/providers/_shared/google/service-account.d.ts +2 -1
  122. package/dist/providers/_shared/google/service-account.js +8 -4
  123. package/dist/providers/_shared/google/workspace.d.ts +6 -210
  124. package/dist/providers/_shared/google/workspace.js +6 -425
  125. package/dist/providers/_shared/rest/compact-index.js +86 -0
  126. package/dist/providers/_shared/rest/kit.js +146 -0
  127. package/dist/providers/_shared/rest/operation-index.js +230 -14
  128. package/dist/providers/_shared/rest/response-media.js +11 -0
  129. package/dist/providers/_shared/rest/value-safety.js +353 -195
  130. package/dist/providers/_shared/sensitive-fields.js +27 -0
  131. package/dist/providers/breeze/api.js +584 -0
  132. package/dist/providers/breeze/index.d.ts +1 -3
  133. package/dist/providers/breeze/index.js +67 -1420
  134. package/dist/providers/breeze/openapi.generated.js +60 -0
  135. package/dist/providers/breeze/skill.generated.js +2 -6
  136. package/dist/providers/breeze/tools.js +696 -0
  137. package/dist/providers/ccb/api.js +496 -0
  138. package/dist/providers/ccb/index.d.ts +15 -37
  139. package/dist/providers/ccb/index.js +77 -1702
  140. package/dist/providers/ccb/lib/constants.d.ts +34 -0
  141. package/dist/providers/ccb/lib/constants.js +74 -0
  142. package/dist/providers/ccb/lib/guide.js +9 -0
  143. package/dist/providers/ccb/lib/verdicts.js +387 -0
  144. package/dist/providers/ccb/openapi.generated.js +397 -0
  145. package/dist/providers/ccb/safety.js +295 -0
  146. package/dist/providers/ccb/skill.generated.js +2 -6
  147. package/dist/providers/ccb/tools.js +853 -0
  148. package/dist/providers/cloudflare/api.js +1066 -0
  149. package/dist/providers/cloudflare/index.d.ts +5 -22
  150. package/dist/providers/cloudflare/index.js +79 -197
  151. package/dist/providers/cloudflare/lib/config.d.ts +16 -0
  152. package/dist/providers/cloudflare/lib/config.js +2 -0
  153. package/dist/providers/cloudflare/lib/discovery.js +29 -0
  154. package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
  155. package/dist/providers/cloudflare/lib/hatch.js +103 -0
  156. package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
  157. package/dist/providers/cloudflare/lib/response-media.js +75 -0
  158. package/dist/providers/cloudflare/openapi.generated.js +4 -3
  159. package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
  160. package/dist/providers/cloudflare/skill.generated.js +2 -10
  161. package/dist/providers/cloudflare/tools.js +360 -0
  162. package/dist/providers/docs/api.js +51 -0
  163. package/dist/providers/docs/index.d.ts +1 -12
  164. package/dist/providers/docs/index.js +53 -1165
  165. package/dist/providers/docs/lib/constants.d.ts +18 -0
  166. package/dist/providers/docs/lib/constants.js +18 -0
  167. package/dist/providers/docs/lib/discovery.js +26 -0
  168. package/dist/providers/docs/lib/requests.js +48 -0
  169. package/dist/providers/docs/lib/schemas.js +168 -0
  170. package/dist/providers/docs/openapi.generated.js +16 -0
  171. package/dist/providers/docs/safety.js +54 -0
  172. package/dist/providers/docs/skill.generated.js +2 -6
  173. package/dist/providers/docs/tools.js +801 -0
  174. package/dist/providers/drive/api.js +177 -0
  175. package/dist/providers/drive/index.d.ts +1 -12
  176. package/dist/providers/drive/index.js +38 -1448
  177. package/dist/providers/drive/lib/constants.d.ts +6 -0
  178. package/dist/providers/drive/lib/constants.js +6 -0
  179. package/dist/providers/drive/lib/contracts.js +532 -0
  180. package/dist/providers/drive/lib/hatch.js +197 -0
  181. package/dist/providers/drive/openapi.generated.js +77 -0
  182. package/dist/providers/drive/safety.js +90 -0
  183. package/dist/providers/drive/skill.generated.js +2 -6
  184. package/dist/providers/drive/tools.js +1290 -0
  185. package/dist/providers/forms/api.js +71 -0
  186. package/dist/providers/forms/index.d.ts +1 -12
  187. package/dist/providers/forms/index.js +49 -1145
  188. package/dist/providers/forms/lib/constants.d.ts +14 -0
  189. package/dist/providers/forms/lib/constants.js +19 -0
  190. package/dist/providers/forms/lib/hatch.js +72 -0
  191. package/dist/providers/forms/openapi.generated.js +23 -0
  192. package/dist/providers/forms/safety.js +34 -0
  193. package/dist/providers/forms/skill.generated.js +2 -8
  194. package/dist/providers/forms/tools.js +1023 -0
  195. package/dist/providers/github/api.js +150 -0
  196. package/dist/providers/github/index.d.ts +4 -5
  197. package/dist/providers/github/index.js +30 -87
  198. package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
  199. package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
  200. package/dist/providers/github/lib/discovery.js +132 -0
  201. package/dist/providers/github/lib/operations.js +58 -0
  202. package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
  203. package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
  204. package/dist/providers/github/openapi.generated.js +1216 -0
  205. package/dist/providers/github/safety.js +216 -0
  206. package/dist/providers/github/skill.generated.js +2 -5
  207. package/dist/providers/github/tools.js +506 -0
  208. package/dist/providers/gmail/api.js +114 -0
  209. package/dist/providers/gmail/index.d.ts +1 -12
  210. package/dist/providers/gmail/index.js +73 -1621
  211. package/dist/providers/gmail/lib/constants.d.ts +13 -0
  212. package/dist/providers/gmail/lib/constants.js +16 -0
  213. package/dist/providers/gmail/lib/index.js +173 -0
  214. package/dist/providers/gmail/lib/schemas.js +176 -0
  215. package/dist/providers/gmail/lib/threading.js +120 -0
  216. package/dist/providers/gmail/openapi.generated.js +92 -0
  217. package/dist/providers/gmail/safety.js +21 -0
  218. package/dist/providers/gmail/skill.generated.js +2 -6
  219. package/dist/providers/gmail/tools.js +1435 -0
  220. package/dist/providers/overflow/api.js +1008 -0
  221. package/dist/providers/overflow/index.d.ts +1 -17
  222. package/dist/providers/overflow/index.js +60 -1488
  223. package/dist/providers/overflow/skill.generated.js +2 -5
  224. package/dist/providers/overflow/tools.js +873 -0
  225. package/dist/providers/planning-center/api.js +467 -0
  226. package/dist/providers/planning-center/index.d.ts +4 -35
  227. package/dist/providers/planning-center/index.js +69 -2445
  228. package/dist/providers/planning-center/lib/constants.d.ts +58 -0
  229. package/dist/providers/planning-center/lib/constants.js +83 -0
  230. package/dist/providers/planning-center/lib/discovery.js +40 -0
  231. package/dist/providers/planning-center/lib/guide.js +39 -0
  232. package/dist/providers/planning-center/lib/json-api.js +83 -0
  233. package/dist/providers/planning-center/lib/verdicts.js +1758 -0
  234. package/dist/providers/planning-center/openapi.generated.js +1764 -0
  235. package/dist/providers/planning-center/safety.js +188 -0
  236. package/dist/providers/planning-center/skill.generated.js +2 -27
  237. package/dist/providers/planning-center/tools.js +1010 -0
  238. package/dist/providers/quickbooks/api.js +299 -0
  239. package/dist/providers/quickbooks/index.d.ts +13 -0
  240. package/dist/providers/quickbooks/index.js +93 -0
  241. package/dist/providers/quickbooks/lib/query.js +118 -0
  242. package/dist/providers/quickbooks/lib/writes.js +168 -0
  243. package/dist/providers/quickbooks/safety.js +222 -0
  244. package/dist/providers/quickbooks/skill.generated.js +5 -0
  245. package/dist/providers/quickbooks/tools.js +117 -0
  246. package/dist/providers/sheets/api.js +133 -0
  247. package/dist/providers/sheets/index.d.ts +4 -16
  248. package/dist/providers/sheets/index.js +61 -1172
  249. package/dist/providers/sheets/lib/constants.d.ts +14 -0
  250. package/dist/providers/sheets/lib/constants.js +14 -0
  251. package/dist/providers/sheets/lib/hatch.js +163 -0
  252. package/dist/providers/sheets/lib/requests.js +105 -0
  253. package/dist/providers/sheets/lib/schemas.js +138 -0
  254. package/dist/providers/sheets/openapi.generated.js +30 -0
  255. package/dist/providers/sheets/safety.js +27 -0
  256. package/dist/providers/sheets/skill.generated.js +2 -7
  257. package/dist/providers/sheets/tools.js +733 -0
  258. package/dist/providers/slides/api.js +147 -0
  259. package/dist/providers/slides/index.d.ts +2 -10
  260. package/dist/providers/slides/index.js +48 -2555
  261. package/dist/providers/slides/lib/constants.d.ts +11 -0
  262. package/dist/providers/slides/lib/constants.js +11 -0
  263. package/dist/providers/slides/lib/requests.js +60 -0
  264. package/dist/providers/slides/lib/schemas.js +565 -0
  265. package/dist/providers/slides/openapi.generated.js +18 -0
  266. package/dist/providers/slides/skill.generated.js +2 -6
  267. package/dist/providers/slides/tools.js +1968 -0
  268. package/dist/providers/stripe/api.js +401 -0
  269. package/dist/providers/stripe/index.d.ts +5 -27
  270. package/dist/providers/stripe/index.js +74 -210
  271. package/dist/providers/stripe/lib/config.d.ts +4 -0
  272. package/dist/providers/stripe/lib/config.js +2 -0
  273. package/dist/providers/stripe/openapi.generated.js +4 -3
  274. package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
  275. package/dist/providers/stripe/skill.generated.js +2 -11
  276. package/dist/providers/stripe/tools.js +77 -0
  277. package/dist/providers/tithely/api.js +531 -0
  278. package/dist/providers/tithely/index.d.ts +17 -0
  279. package/dist/providers/tithely/index.js +86 -965
  280. package/dist/providers/tithely/skill.generated.js +2 -5
  281. package/dist/providers/tithely/tools.js +426 -0
  282. package/dist/providers/vercel/api.js +551 -0
  283. package/dist/providers/vercel/index.d.ts +9 -25
  284. package/dist/providers/vercel/index.js +65 -260
  285. package/dist/providers/vercel/lib/config.d.ts +2 -0
  286. package/dist/providers/vercel/lib/config.js +2 -0
  287. package/dist/providers/vercel/lib/index.js +117 -0
  288. package/dist/providers/vercel/lib/logs.js +100 -0
  289. package/dist/providers/vercel/lib/pin.js +270 -0
  290. package/dist/providers/vercel/openapi.generated.js +10 -5
  291. package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
  292. package/dist/providers/vercel/skill.generated.js +2 -7
  293. package/dist/providers/vercel/tools.js +642 -0
  294. package/dist/redaction-identity.js +47 -0
  295. package/dist/redaction-marker.js +125 -0
  296. package/dist/registry.d.ts +21 -15
  297. package/dist/registry.js +409 -376
  298. package/dist/routes/access-tokens.js +1 -1
  299. package/dist/routes/actor-labels.js +1 -1
  300. package/dist/routes/connect.js +1 -1
  301. package/dist/routes/dispatch.js +103 -0
  302. package/dist/routes/mcp.js +170 -79
  303. package/dist/routes/oauth.js +6 -1
  304. package/dist/routes/operator.js +2 -2
  305. package/dist/routes/shared.d.ts +5 -4
  306. package/dist/routes/shared.js +244 -177
  307. package/dist/routes/skills.js +10 -8
  308. package/dist/routes/ui-config.js +27 -3
  309. package/dist/routes/ui-data.js +21 -20
  310. package/dist/routes/ui.js +1 -1
  311. package/dist/runtime/call-admission.js +0 -26
  312. package/dist/runtime/call-entry.js +30 -0
  313. package/dist/runtime/call.js +552 -0
  314. package/dist/runtime/connector-scope.js +7 -0
  315. package/dist/runtime/credential-probe.js +34 -0
  316. package/dist/runtime/executor.js +40 -0
  317. package/dist/runtime/native-storage.js +63 -0
  318. package/dist/runtime/registry.js +27 -0
  319. package/dist/runtime/request-scope.js +72 -0
  320. package/dist/runtime/run.js +15 -7
  321. package/dist/runtime/shared-read.js +34 -5
  322. package/dist/runtime/storage.js +34 -21
  323. package/dist/sent-secrets.d.ts +194 -37
  324. package/dist/sent-secrets.js +1743 -340
  325. package/dist/server.js +5 -56
  326. package/dist/skills.js +322 -223
  327. package/dist/sqlite.js +15 -14
  328. package/dist/storage/d1-copy.js +255 -0
  329. package/dist/storage/d1-driver.js +15 -0
  330. package/dist/storage/keys.js +28 -0
  331. package/dist/storage/sql-driver.js +88 -0
  332. package/dist/storage/sql-native.js +544 -0
  333. package/dist/storage/sql-schema.d.ts +6 -0
  334. package/dist/storage/sql-schema.js +15 -0
  335. package/dist/storage/sql.d.ts +14 -20
  336. package/dist/storage/sql.js +29 -535
  337. package/dist/timeout.js +0 -26
  338. package/dist/tool-safety.js +20 -10
  339. package/dist/types.d.ts +9 -109
  340. package/dist/ui.d.ts +1 -1
  341. package/dist/ui.js +0 -1
  342. package/dist/upward.js +3 -0
  343. package/dist/usage-guide.js +3 -1
  344. package/dist/validate.d.ts +4 -2
  345. package/dist/validate.js +86 -9
  346. package/dist/version.d.ts +1 -1
  347. package/dist/version.js +1 -1
  348. package/dist/worker.js +312 -292
  349. package/dist/workspace-subject.js +5 -1
  350. package/dist/write-outcome.js +454 -34
  351. package/documentation/README.md +28 -7
  352. package/documentation/architecture.md +2409 -255
  353. package/documentation/auth.md +867 -114
  354. package/documentation/code-mode.md +49 -16
  355. package/documentation/deploying.md +164 -20
  356. package/documentation/hosted-mcp-migration.md +460 -0
  357. package/documentation/integrating.md +129 -144
  358. package/documentation/meta-tools.md +409 -32
  359. package/documentation/operating.md +27 -3
  360. package/documentation/operator-tests.md +3 -0
  361. package/documentation/operator-ui.md +84 -48
  362. package/documentation/provider-auth-migration.md +16 -63
  363. package/documentation/provider-migration-0.29.md +10 -5
  364. package/documentation/provider-migration-0.35.md +975 -0
  365. package/documentation/providers.md +1347 -0
  366. package/examples/worker/AGENTS.md +23 -9
  367. package/examples/worker/README.md +254 -135
  368. package/examples/worker/alchemy.run.ts +116 -0
  369. package/examples/worker/gitignore +10 -0
  370. package/examples/worker/src/connecta.config.ts +23 -21
  371. package/examples/worker/wrangler.jsonc +14 -5
  372. package/guides/connecting-agents.md +157 -0
  373. package/guides/how-it-works.md +156 -0
  374. package/guides/safety-and-trust.md +153 -0
  375. package/package.json +30 -50
  376. package/templates/node/.env.example +3 -0
  377. package/templates/node/README.md +22 -2
  378. package/templates/node/docker-compose.yml +1 -0
  379. package/templates/node/package.json +3 -2
  380. package/templates/node/src/connecta.config.ts +8 -0
  381. package/dist/catalog-drift.d.ts +0 -138
  382. package/dist/catalog-drift.js +0 -578
  383. package/dist/operator-ui/agents.d.ts +0 -74
  384. package/dist/operator-ui/agents.js +0 -104
  385. package/dist/operator-ui/config-summary.js +0 -565
  386. package/dist/operator-ui/connection-map.js +0 -376
  387. package/dist/operator-ui/endpoints.js +0 -221
  388. package/dist/operator-ui/fix-prompts.d.ts +0 -18
  389. package/dist/operator-ui/fix-prompts.js +0 -87
  390. package/dist/operator-ui/health.js +0 -52
  391. package/dist/operator-ui/member-page.js +0 -97
  392. package/dist/operator-ui/overview.js +0 -123
  393. package/dist/operator-ui/setup-commands.js +0 -51
  394. package/dist/operator-ui/view.d.ts +0 -401
  395. package/dist/operator-ui/view.js +0 -705
  396. package/dist/providers/_shared/request-token.js +0 -83
  397. package/dist/providers/_shared/rest/dispatch.js +0 -41
  398. package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
  399. package/dist/providers/_shared/rest/tools.d.ts +0 -141
  400. package/dist/providers/_shared/rest/tools.js +0 -569
  401. package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
  402. package/dist/providers/basecamp/index.d.ts +0 -45
  403. package/dist/providers/basecamp/index.js +0 -997
  404. package/dist/providers/basecamp/skill.generated.js +0 -9
  405. package/dist/providers/cloudflare/rest.d.ts +0 -37
  406. package/dist/providers/cloudflare/rest.js +0 -1097
  407. package/dist/providers/github/hosted.js +0 -218
  408. package/dist/providers/github/rest.js +0 -321
  409. package/dist/providers/infisical/index.d.ts +0 -7
  410. package/dist/providers/infisical/index.js +0 -713
  411. package/dist/providers/infisical/skill.generated.js +0 -8
  412. package/dist/providers/intercom/index.d.ts +0 -24
  413. package/dist/providers/intercom/index.js +0 -121
  414. package/dist/providers/intercom/skill.generated.js +0 -8
  415. package/dist/providers/linear/index.d.ts +0 -55
  416. package/dist/providers/linear/index.js +0 -537
  417. package/dist/providers/linear/skill.generated.js +0 -8
  418. package/dist/providers/mixpanel/index.d.ts +0 -59
  419. package/dist/providers/mixpanel/index.js +0 -472
  420. package/dist/providers/mixpanel/operations.js +0 -20
  421. package/dist/providers/mixpanel/rest.js +0 -603
  422. package/dist/providers/mixpanel/skill.generated.js +0 -10
  423. package/dist/providers/mixpanel/value-safety.js +0 -16
  424. package/dist/providers/notion/index.d.ts +0 -67
  425. package/dist/providers/notion/index.js +0 -368
  426. package/dist/providers/notion/openapi.generated.js +0 -75
  427. package/dist/providers/notion/rest.d.ts +0 -24
  428. package/dist/providers/notion/rest.js +0 -1226
  429. package/dist/providers/notion/skill.generated.js +0 -11
  430. package/dist/providers/notion/value-safety.js +0 -56
  431. package/dist/providers/revenuecat/index.d.ts +0 -51
  432. package/dist/providers/revenuecat/index.js +0 -697
  433. package/dist/providers/revenuecat/skill.generated.js +0 -11
  434. package/dist/providers/stripe/rest.d.ts +0 -14
  435. package/dist/providers/stripe/rest.js +0 -402
  436. package/dist/providers/vercel/rest.d.ts +0 -20
  437. package/dist/providers/vercel/rest.js +0 -1020
  438. package/dist/providers/wispr-flow/index.d.ts +0 -14
  439. package/dist/providers/wispr-flow/index.js +0 -117
  440. package/dist/providers/wispr-flow/skill.generated.js +0 -8
  441. /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
@@ -336,7 +426,7 @@ client. A raw Worker stream established the runtime behavior; a custom auth
336
426
  provider returning a synthetic streaming 401 exercised Connecta's real admission
337
427
  wrapper. Each case had its own Connecta instance with one request permit and no
338
428
  queue. Health checks and subsequent MCP requests confirmed the same isolate ID.
339
- The [recorded observations](https://github.com/zackbart/connecta/blob/a0ac904513fac735582b4bc9c817a48300134328/scripts/probes/worker-disconnect-2026-09-25.json)
429
+ The [recorded observations](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect-2026-09-25.json)
340
430
  contain all twelve cases, including served flags and client-end timing.
341
431
 
342
432
  | Response and flag | Raw stream abort / cancel | Connecta source abort / cancel | Admission after response; next request |
@@ -364,29 +454,55 @@ 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;
370
486
  the first MCP request after the deadline returned all eight tools with 200 in
371
487
  the original isolate. Admission then reported zero active requests. Without the
372
488
  flag, the source still recorded no abort or cancellation, so recovery did not
373
- rely on runtime cleanup. The [recovery observations](https://github.com/zackbart/connecta/blob/32d8e94/scripts/probes/worker-disconnect-recovered-2026-09-25.json)
489
+ rely on runtime cleanup. The [recovery observations](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect-recovered-2026-09-25.json)
374
490
  also retain two inconclusive queue-handoff cases: concurrent client connections
375
491
  reached different isolates and could not establish an abandoned queued waiter.
376
- A [second trial using HTTP/2](https://github.com/zackbart/connecta/blob/b174812/scripts/probes/worker-disconnect-h2-2026-09-25.json)
492
+ A [second trial using HTTP/2](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect-h2-2026-09-25.json)
377
493
  repeated the twelve passing core cases and verified queued cancellation with
378
494
  request signals enabled. Even on one HTTP/2 connection, the no-signal queue case
379
495
  crossed isolates and remained inconclusive. The Node and workerd regression
380
496
  suites cover expired waiters, orphaned handoffs, and identity-safe late release
381
497
  deterministically.
382
498
 
383
- To repeat from a repository checkout, run
384
- `node scripts/probes/worker-disconnect.mjs` with an authenticated Wrangler.
385
- The script is not in the npm package. It deploys a disposable Worker containing
386
- only synthetic data, verifies the served configuration, saves observations under `eval/results/`,
387
- and deletes the Worker in `finally`. It uses no `waitUntil`, which would change
388
- the lifetime being measured. The production runtime build number was not
389
- exposed by the deployment; these findings are tied to the date and flags above.
499
+ The [historical probe source](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect.mjs)
500
+ and observations are retained in Git history, outside the current checkout and
501
+ npm package. The probe deployed a disposable Worker containing only synthetic
502
+ data, verified the served configuration, saved observations, and deleted the
503
+ Worker in `finally`. It used no `waitUntil`, which would change the lifetime
504
+ being measured. The production runtime build number was not exposed by the
505
+ deployment; these findings are tied to the date and flags above.
390
506
 
391
507
  ### Production Access bypass for agent endpoints
392
508
 
@@ -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,91 +1256,175 @@ 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.
971
-
972
- The generic REST connector (`src/providers/_shared/rest/`) serves
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.
1326
+
1327
+ The indexed hatch (`src/http/hatch.ts`) 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`)
1331
+ into `openapi.generated.ts`. A `vendored: true` pin compiles offline from
1332
+ the reviewed conversion in `evidence/openapi.document.json`, or derives a
1333
+ deterministic Discovery conversion from its pinned original as described below.
1334
+ The generated header records the converted document's digest beside the pin's,
1335
+ so offline freshness sees a changed conversion.
976
1336
  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
978
- its handler, since classification is per tool name. Vendors configure only a
979
- transport, failure mapper, scope, framing, cursor extraction, a reviewed
980
- value-safety table (required; see below), argument-dependent refusals, and an
981
- idempotency header, plus optional hooks: `path` (fill default ids before
982
- matching), `admit` (an awaited check in `callRest` every tool passes, such as
983
- Cloudflare's account and zone pin), `result` (unwrap a vendor envelope;
984
- redaction runs after it and before cursors and `select`), a reviewed
985
- request-header allowlist, text bodies, and index `slashParams` that admit an
986
- encoded `/` in a final key segment (`test/rest-connector.test.ts`).
1337
+ transport. Resolved path parameters are validated before authentication or dispatch,
1338
+ including pinned enums and numeric bounds. Number path values must decode to canonical finite ASCII decimals without
1339
+ exponents, hexadecimal, a leading plus or whitespace. Integer path values must decode to
1340
+ canonical ASCII decimal safe integers: `0`, positive decimals without leading
1341
+ zeros, or negative decimals without leading zeros. `-0`, leading `+`, whitespace,
1342
+ exponents, decimal points and non-ASCII digits are refused. Negative integers
1343
+ remain valid when the pin has no minimum excluding them. String-valued ids keep
1344
+ their declared string contract. `_api_read` admits only operations with explicit
1345
+ reviewed read verdicts, including GET/HEAD and read-only POSTs where declared;
1346
+ classification is per tool name. Providers declare a confined kit connection,
1347
+ a value-safety table (required;
1348
+ see below), and any framing, scope, paging or refusal hooks through the hatch
1349
+ binding. The connection owns authentication, transport, failure mapping and
1350
+ result safety. An index's `slashParams` can admit an encoded `/` in a reviewed
1351
+ final key segment. Current hatch admission, paging, privacy and one-send
1352
+ outcomes are exercised by the required `eval:outcomes` tasks against stateful
1353
+ vendors and HTTP MCP deployments. `test/provider-hostile-response.node.test.ts`
1354
+ commits real HTTP writes before returning HTML, malformed JSON or an oversized
1355
+ acknowledgement, then reconciles vendor state with exactly one physical send.
1356
+ `test/provider-privacy-journey.node.test.ts` checks credential echoes over HTTP.
1357
+ Focused provider suites retain MIME/draft restrictions, revision conflicts,
1358
+ CCB routing, Planning Center product exclusions and deployment pins. Native
1359
+ HTTP framing, cursor and request-lifetime fault probes remain in the HTTP suites;
1360
+ catalog copies and decoder/adapter parity matrices are retired under #1072.
1361
+ No legacy REST tool adapter remains.
987
1362
  Large documents opt into generator options that drop restated operation ids
988
1363
  and plain path parameters and cap one operation's details (`opBudget`).
1364
+ `numericBounds: true` preserves documented numeric minimum/maximum constraints
1365
+ in request schemas and enforces them before dispatch, including numeric query
1366
+ strings. Existing indexes without those optional constraints are unchanged.
1367
+ The compiler separately records response property paths from the lowest
1368
+ declared JSON success status (then `2XX`), resolving local references and
1369
+ composition. It includes top-level and list-item properties (root arrays or
1370
+ an envelope's immediate arrays), and shares paths and rows in a lazy `OpenApiData.fields`
1371
+ JSON string. Traversal stops at 1,024 schema visits, 24 reference/composition
1372
+ levels, 256 paths or 200 characters per path; overflow or an unresolved
1373
+ reference omits that operation's whole response-field contract. Explicitly open
1374
+ roots (including root-array items) also leave fields absent. Empty,
1375
+ schemaless and non-JSON responses declare no fields. Request shrinking and
1376
+ endpoint contract digests do not depend on this metadata.
1377
+ Fields remain optional additive metadata, so a previously compiled
1378
+ index without the source bytes remains usable without inventing a response
1379
+ contract. Vercel's step-2 pin review restores response metadata from the reviewed public
1380
+ document; operations without declared response properties still refuse fields. Breeze's pin declares no response
1381
+ 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
1382
  `check:providers-generated` checks each index header against its source record
990
1383
  offline; `providers:check` reports a pin that differs from the live document.
991
1384
  No maintained provider selects an implementation with `surface` any more. See
992
1385
  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.
1386
+ [provider auth migration](./provider-auth-migration.md).
1387
+
1388
+ #### Generated index storage (contributor contract)
1389
+
1390
+ Generated format 5 stores the same logical `OpenApiData` as earlier formats.
1391
+ The shared renderer interns complete parameter rows by their exact JSON text,
1392
+ including descriptions, required flags and compact schema references. Each
1393
+ operation records parameter-row ids and its unchanged serialized body. Shared
1394
+ schema definitions, response fields, lossless request graphs, operation order,
1395
+ pins and all compiler shrink options retain their existing meaning. The two
1396
+ indexes derived directly from kit declarations continue using their plain
1397
+ logical representation.
1398
+
1399
+ `compactOpenApi` exposes the exact original `details` string through an
1400
+ enumerable, replaceable property. `OperationIndex` parses definitions once per
1401
+ instance and decodes each operation row on first use. Each row occurrence gets
1402
+ its own parsed parameters; dictionary sharing introduces no new mutable object
1403
+ sharing between operations or instances. Existing shared schema references and
1404
+ their mutation behaviour remain intact. Ordinary logical indexes still use
1405
+ `JSON.parse`. Construction neither expands the complete details string nor
1406
+ computes an identity hash. Cursor binding continues hashing the same canonical
1407
+ logical data and policy, with no encoding metadata in its preimage, preserving
1408
+ existing cursor bytes for unchanged content.
1409
+
1410
+ The runtime codec uses no platform-specific imports, recursive references or
1411
+ compression dependency. Parameter ids must be nonnegative safe integers naming
1412
+ existing strings. Reconstructing a row or full details text is bounded to 64 Mi
1413
+ UTF-16 code units; full reconstruction must match its recorded exact length.
1414
+ These physical decode checks do not change request validation, schema traversal,
1415
+ search, refusal, result or discovery budgets. Generation and offline roundtrip
1416
+ tests must preserve every pinned logical contract; storage changes never accept
1417
+ new upstream evidence. The compaction suites cover exact strings,
1418
+ native Web Crypto identity, malformed references, lazy decoding, mutation
1419
+ semantics and every maintained provider.
996
1420
 
997
1421
  #### Value safety for REST vendors
998
1422
 
999
1423
  The bar ([decision 0005](https://github.com/zackbart/connecta/blob/main/decisions/0005-auth-selects-implementation.md)):
1000
1424
  no REST tool response, success or error, returns a credential that grants
1001
1425
  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
1426
+ reviewed named tool exists to return it. The threat model assumes an honest server; secrets people type into free
1427
+ text are out of scope. Field-by-field redaction over a whole vendor API does not converge, so
1005
1428
  every vendor ships one reviewed table through one mechanism
1006
1429
  (`src/providers/_shared/rest/value-safety.ts`):
1007
1430
 
@@ -1012,12 +1435,14 @@ every vendor ships one reviewed table through one mechanism
1012
1435
  signing, webhooks, drains, transfer, claim, and invite codes). It is the
1013
1436
  union of the detectors Cloudflare and Vercel shipped first, and
1014
1437
  deliberately over-inclusive. `providers:spec` writes
1015
- `value-safety.candidates.json` beside the index when the source record sets
1438
+ `evidence/value-safety.json` (`value-safety.candidates.json` until the
1439
+ provider's step 1) when the source record sets
1016
1440
  `options.valueSafety` (vendor `operationWords`; Stripe's `expansions: false`
1017
1441
  skips id-or-object expansions, which are their own operations' candidates),
1018
1442
  stamped with the pinned digest, the detector format, and its options. The
1019
1443
  file is test evidence and never ships.
1020
- 2. **One reviewed verdict per candidate**, in the vendor's `value-safety.ts`:
1444
+ 2. **One reviewed verdict per candidate**, in the vendor's `safety.ts`
1445
+ (`value-safety.ts` until its step 1):
1021
1446
  `refuse` (the generic tools refuse it before transport, naming the
1022
1447
  operation and the reason), `redact` (reviewed field paths), or `safe`, each
1023
1448
  with a reason. Paths are dot paths into the data a tool returns: a list on
@@ -1038,8 +1463,8 @@ every vendor ships one reviewed table through one mechanism
1038
1463
  otherwise. `providers:spec` derives the binding fields from the pinned
1039
1464
  schema, and the harness fails until the condition lists each one. Argument-dependent refusals (Stripe expansions,
1040
1465
  Vercel `decrypt`) stay in the vendor's `refuse` hook.
1041
- 3. **Redaction on every success body**, in `callRest` after `result` and
1042
- before cursors and `select`, so named tools, HEAD data, and uploads pass
1466
+ 3. **Redaction on every success body**, in the kit connection after `result`
1467
+ and before cursors and `fields`, so named tools, HEAD data, and uploads pass
1043
1468
  through it too: the operation's reviewed paths, then the key-name
1044
1469
  heuristic as defense in depth. The heuristic is the union of the former
1045
1470
  vendor engines: a credential word anywhere in a non-metadata name removes
@@ -1052,10 +1477,22 @@ every vendor ships one reviewed table through one mechanism
1052
1477
  reviews the pinned spec's free-text fields for embedded vendor-minted links
1053
1478
  and records its verdict and source limitations beside its value-safety table.
1054
1479
  Stripe, Vercel, and Cloudflare use **reviewed paths plus global signing
1055
- parameters**. Literal AWS SigV4/Google V4 `X-Amz-`/`X-Goog-` Signature,
1480
+ parameters**. Known AWS SigV4/Google V4 `X-Amz-`/`X-Goog-` Signature,
1056
1481
  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
1482
+ values, including values written with decimal, hexadecimal or named HTML
1483
+ character references. References are read whole as value data; decoding
1484
+ only finds nested-reference boundaries and classifies a placeholder's
1485
+ remainder. No decoded replacement is returned. Possible named references
1486
+ are read fail-closed with their required semicolon and bounded HTML name
1487
+ length. Numeric references also accept an omitted semicolon, and `amp`
1488
+ may omit its semicolon immediately before `#`. Signing separators accept
1489
+ nested ampersand references around decimal, hexadecimal or named `=`
1490
+ references. Recognized name representations retain their source spelling
1491
+ and use the same fixed signing-name vocabulary and value boundaries.
1492
+ A literal query separator such as `&name=alice` still ends the signing
1493
+ value. Ordinary query strings and prose retain their original spelling.
1494
+ Generic credential names (`code`, `key`, `token`, `signature`, `sig`, etc.)
1495
+ and credential-looking fragments apply only to reviewed diagnostic
1059
1496
  paths and vendor error text. Matching destinations lose their entire query
1060
1497
  and fragment. The tables list exact operations/resource paths with evidence:
1061
1498
  Stripe charge failure/outcome messages, Vercel deployment failures and
@@ -1064,6 +1501,9 @@ every vendor ships one reviewed table through one mechanism
1064
1501
  invocation maps. The shared review harness rejects embedded-URL operation
1065
1502
  keys absent from each vendor's pinned index and invalid path syntax.
1066
1503
  The current Vercel pin was reviewed from its full response document; its unversioned URL does not retain older documents.
1504
+ Vercel project creation registers every submitted environment-variable value
1505
+ before dispatch, so echoes in response metadata, keys and prose are withheld
1506
+ before field selection while safe project and variable metadata remain.
1067
1507
  Generic parameters in descriptions, metadata, nicknames, billing footers,
1068
1508
  notes, DNS TXT, KV values and operator HTML retain their original bytes,
1069
1509
  including whole URL strings. Ordinary code/key URLs can be false positives
@@ -1095,42 +1535,85 @@ every vendor ships one reviewed table through one mechanism
1095
1535
  (decoded bytes are at most three quarters of encoded characters). Cloudflare
1096
1536
  KV additionally enforces its documented 25 MiB decoded value limit;
1097
1537
  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.
1538
+ The authenticated MCP `admission.requests.maxBodyBytes` cap and any smaller
1539
+ host cap still apply; response and raw-upload limits do not bound JSON fields.
1100
1540
  JSON admission iteratively checks every key and value, with no separate
1101
1541
  depth or node-count ceiling below the accepted request's size. Cyclic
1102
1542
  direct arguments refuse with `invalid_args`; repeated non-cyclic references
1103
1543
  are inspected. The scans remain linear in the supplied and decoded bytes.
1104
1544
  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]`
1545
+ link or omit the field. This link scanner alone treats a marker outside a
1546
+ destination as ordinary text. Kit admission additionally refuses whole
1547
+ marker values and any marker containment in paths and queries; longer prose
1548
+ in body fields may mention a marker (see Contributor provider kit).
1549
+ Reviewed text paths withhold credential-bearing URL queries and fragments.
1550
+ AWS and Google signing parameter values are withheld at every host.
1551
+ `carriesWithheldLink(value)` is
1552
+ a shared write-admission helper that refuses a write before dispatch
1553
+ when a link's own query/fragment carries the exact `[redacted]`
1126
1554
  placeholder, plain or up to four layers encoded. Admission resumes at
1127
1555
  each actual Markdown destination's close, including adjacent links and
1128
1556
  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.
1557
+ line ending before the destination are recognized. This scanner treats a marker
1558
+ after the close as ordinary text; kit admission applies the whole-value and
1559
+ path/query rules described above. This boundary is independent of redaction's fail-closed
1560
+ span. Each string is scanned alone; split rich-text runs are outside this
1561
+ scan's threat model.
1562
+ Sent-secret redaction runs before this step, in the provider kit's parse
1563
+ pass, so a rule that decides on a value can read the `[redacted]`
1564
+ placeholder where the vendor wrote what the rule matches. Every such
1565
+ decision input fails closed through one check, `redactionAltered` in
1566
+ `src/redaction-marker.ts`. A resource or embedded-URL type discriminator
1567
+ that carries the placeholder applies every resource rule's paths and
1568
+ withheld fields, never a verbatim URL. A label (`key`, `name`,
1569
+ `target.key`) or `type` that carries it makes the record a labelled or
1570
+ typed secret, a header, cookie, or query rule, and an environment item; an
1571
+ environment list string that carries it is withheld like `NAME=value`. A
1572
+ whole string whose scheme it may have taken (no whitespace, and only
1573
+ scheme characters before the placeholder) is withheld, at a destination
1574
+ key too. A parameter name it altered (the placeholder inside a run of name
1575
+ characters that ends at a plain or encoded `=`, and not directly after an
1576
+ `=`) loses its value wherever signing parameters are scanned, and in
1577
+ reviewed text withholds the whole link, as does a placeholder opening a
1578
+ word where a link's scheme and host would be; a fragment it altered
1579
+ withholds the query and fragment. A key that carries it (text a result
1580
+ hook parses after the kit redacted it whole, such as NDJSON and log
1581
+ streams) loses its value, and its record's key-based decisions take their
1582
+ protective outcome, route header pairs included. A placeholder can also
1583
+ stand for a credential's public prefix (`sk_live_`, `whsec_`, or a signing
1584
+ parameter's name and `=`) and leave its remainder, which with the prefix
1585
+ known rebuilds the credential however short or encoded it is. Wherever
1586
+ signing parameters are scanned, a placeholder followed by any of a value's
1587
+ base64 or base64url characters (letters, digits, `+`, `/`, `=`, `-`, `_`)
1588
+ or `%` escapes is withheld with them through the end of the value
1589
+ (`splitRemainder`), so a tail of `+/` alone goes too. A reference in an
1590
+ inferred remainder counts when it decodes to credential characters or an
1591
+ ampersand. Without a semicolon, numeric digits may run into the following
1592
+ value: a credential character spelled by the first 1–3 decimal or 1–2 hex
1593
+ digits after leading zeros also counts. Other references end that
1594
+ remainder, preserving text after spacing or punctuation references.
1595
+ Known signing values read references whole regardless of the character
1596
+ they spell. A value's end never splits
1597
+ a placeholder. In reviewed links, a query component that carries the
1598
+ placeholder and has no readable name withholds the query. What
1599
+ stays visible cannot complete a credential: other punctuation alone after a
1600
+ placeholder (`.`, `,`, `;`, `!`), which no base64 value contains, and text
1601
+ past a value's end (whitespace, a quote, an angle bracket, `&`, `#`, `)`,
1602
+ `]`, a backtick or a backslash), except where that syntax is part of a
1603
+ character reference read whole. A bare numeric reference body (`#50;`,
1604
+ `#x32;`, with optional semicolons and nested ampersand continuations) can
1605
+ remain when redaction removes part of a reference. It is read as value
1606
+ data, so `#` ends a signing value only when it does not open such a body.
1607
+ Numeric fragments directly after a signing value are withheld too.
1608
+ A following parameter is decided by its own name. Value safety's own
1609
+ placeholders are not alterations: `@keys` map keys, a withheld value right
1610
+ after `=`, and a withheld `#[redacted]` fragment. More is withheld only
1611
+ when a private value occurs in a decision input, and then deliberately:
1612
+ a placeholder inside a base64 value before its `=` padding counts as a
1613
+ parameter name, so its link goes whole; an altered fragment withholds the
1614
+ query and fragment; a Content-Type redaction altered on a body that is not
1615
+ JSON leaves it unreadable (see "Contributor provider kit"); a vendor key
1616
+ spelled with the placeholder loses its value.
1134
1617
  4. **Errors.** A reviewed operation, or an unreviewed one whose request
1135
1618
  accepts a credential-named field, answers failures with the vendor's codes
1136
1619
  and status but not its text, unless its verdict is wrapped in
@@ -1143,13 +1626,15 @@ spec: every `redact` path and `keep` must name something the operation's
1143
1626
  response schema can return (through `options.valueSafety.dataRoot` for an
1144
1627
  envelope such as Cloudflare's `result`), and every resource rule path
1145
1628
  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).
1629
+ into the candidates file and fails on the rest, unless the table's `absent`
1630
+ field (`value-safety.absent.json` until the provider's step 1) acknowledges
1631
+ them with a reason (shared verdicts applied to operations whose schema lacks a
1632
+ path, or fields a schema leaves undeclared; such paths still apply at
1633
+ runtime).
1150
1634
 
1151
1635
  `test/fixtures/value-safety.ts` is the shared harness each vendor's
1152
- `value-safety.node.test.ts` runs; `reviewProblems` is pure, and
1636
+ `value-safety.node.test.ts` (`provider.safety.node.test.ts` after its step 1)
1637
+ runs; `reviewProblems` is pure, and
1153
1638
  `test/value-safety-harness.node.test.ts` proves wrong paths fail it. It fails
1154
1639
  on candidates from another pin, format, or option set; an unreviewed
1155
1640
  candidate; a verdict for an operation the index lacks; a table path the stamp
@@ -1164,89 +1649,62 @@ verdict count.
1164
1649
  `RestVendor.valueSafety` is required, so a new REST vendor cannot ship without
1165
1650
  a table.
1166
1651
 
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.
1652
+ Remote MCP tools classify from server annotations and deployment policy.
1653
+ Only `readOnlyHint === true` with `destructiveHint !== true` establishes a
1654
+ source read; missing or contradictory hints mean write. Deployment overrides
1655
+ in `ConnectaConfig.classification` choose effective `read`, `write`, or `hidden`.
1656
+ They cannot relax source replay safety. Effective or source write makes a call
1657
+ write-like on every resend and recovery path. Source classifications travel in
1658
+ private metadata with host-owned tool clones and stay out of persisted catalogs.
1659
+ A leftover `classify` throws and names `ConnectaConfig.classification`.
1660
+ There are no hosted review maps, pins, drift comparisons or review UI fields.
1661
+ Catalog refresh activity still compares successive accepted catalog fingerprints
1662
+ and reports only added, removed and changed counts in the caller's partition.
1663
+
1664
+ #### Behavioral evidence (contributor)
1665
+
1666
+ The characterization snapshot engine, provider JSON snapshots, source scheduler
1667
+ and its self-tests are retired under
1668
+ [#1072](https://github.com/zackbart/connecta/issues/1072). Provider changes no
1669
+ longer require golden enrollment, snapshot updates, byte equivalence or nightly
1670
+ capture determinism. Production invariants remain unchanged.
1671
+
1672
+ Use the existing stateful fixture vendors and published-edge deployment adapters:
1673
+
1674
+ - `pnpm run eval:outcomes` drives representative joins, retained paging, endpoint
1675
+ refusal, auth handoff, value safety and refund recovery. Graders inspect
1676
+ independent downstream state and the physical mutation ledger, and reject
1677
+ wrong targets, duplicate writes, missing completion and interrupted trials.
1678
+ - `pnpm run eval:smoke` boots the Node template, Node eval deployments and native
1679
+ Workers deployments, including the actual QuickBooks provider, then exercises
1680
+ MCP and the operator page.
1681
+ - `test/provider-privacy-journey.node.test.ts` sends credentials through real
1682
+ HTTP MCP and a maintained API provider. Unknown vendor fields and keys carry
1683
+ raw, escaped, percent-encoded, base64 and Basic echoes through direct calls,
1684
+ QuickJS programs, failure and recovery. The independent decoder in
1685
+ `test/helpers/secret-echoes.ts` checks the wire responses for secret fragments;
1686
+ it does not normalize them to placeholders or compare saved output.
1687
+ - `test/provider-failure-sweep.test.ts` checks retained catalog examples for
1688
+ dispatched failure, classification, auxiliary and composite write outcomes.
1689
+ New providers use the required deployment outcomes above. Existing
1690
+ `test/sent-secrets.test.ts`, `test/sent-secret-output-boundary.test.ts`,
1691
+ `test/request-secret-redaction.test.ts` and `test/catalog-secret-redaction.test.ts`
1692
+ cover secrecy before projection, truncation, caching, paging and diagnostics.
1693
+ - `test/write-evidence.node.test.ts`, `test/remote-mcp-attempts.node.test.ts` and
1694
+ `test/oauth-credential-attempts.node.test.ts` inspect physical socket sends.
1695
+ Native transport parity, OAuth and write-provenance suites retain their
1696
+ workerd coverage. The shared throwaway RSA key in `test/fixtures/rsa.ts` is
1697
+ used by write-provenance and unknown-write-advice fixtures.
1698
+ - The request, catalog, auxiliary-call, renewal and Promise-boundary lifetime
1699
+ suites, plus `test/auth-admission-binding.test.ts`, exercise cancellation,
1700
+ held native work and garbage collection. These test actual request ownership;
1701
+ the retired capture scheduler's source guards were only harness constraints.
1702
+
1703
+ Add a compact fault journey only for a meaningful outcome those suites cannot
1704
+ observe. Reuse their fixtures and adapters; do not reintroduce a snapshot
1705
+ format, module loader or virtual scheduler. Workerd enrollment is for native
1706
+ fetch, streams, Web Crypto, lifetime and request-byte behavior, as listed in
1707
+ `vitest.config.ts`; pure logic runs on Node.
1250
1708
 
1251
1709
  ## Optional deployment modules
1252
1710
 
@@ -1323,7 +1781,7 @@ flight's deadline, never touching the owner's signal or response.
1323
1781
 
1324
1782
  Nothing reachable from `src/index.ts` may import a `node:` builtin, so the same
1325
1783
  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
1784
+ (the `node:http` adapter and its owned `http/node-transport.ts` undici dispatcher), `src/sqlite.ts` (`node:sqlite`), and the QuickJS process pool
1327
1785
  (`src/executors/quickjs.ts` plus its child) — each sit behind an explicit subpath
1328
1786
  and must stay unreachable from the root. `./auth/clerk` is separate because
1329
1787
  `@clerk/backend` is an optional peer rather than a dependency, and
@@ -1340,7 +1798,7 @@ shape from building, in someone else's repository rather than this one.
1340
1798
 
1341
1799
  ## Effect inside
1342
1800
 
1343
- The core runs on stable Effect v4 through a compatible `^4.0.0` range; no API a deployment
1801
+ The core runs on Effect v4, pinned to exactly `4.0.5`; no API a deployment
1344
1802
  touches does. `createConnecta`,
1345
1803
  `remoteMcp()`, `api()`, the `Connector` contract, and every shipped `.d.ts`
1346
1804
  are Promise-shaped and name no Effect type, so a connector author never meets a
@@ -1411,14 +1869,254 @@ storage is the root's namespaced to its principal.
1411
1869
  | 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
1870
  | 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
1871
  | 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. |
1872
+ | `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
1873
  | 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
1874
  | 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
1875
  | 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. |
1876
+ | 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
1877
  | 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
1878
  | 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
1879
 
1880
+ ### Native registry and request services (contributor)
1881
+
1882
+ Registry catalog loading, classification, OAuth handoffs, status, invalidation,
1883
+ credential readiness, and result stashing have private Effect programs in
1884
+ `src/runtime/registry.ts`. Static class blocks install those programs while
1885
+ preserving the published Registry declaration, including its private member
1886
+ names. `registryProgram(view, method, ...args)` checks the installed method's
1887
+ identity; a custom or replaced method remains a Promise leaf. A Promise shell
1888
+ selects its original class implementation through `registryEdgeProgram`, so a
1889
+ wrapper can call the original without redispatching to itself. Catalog search,
1890
+ describe, resolution, and listing compose these programs directly. The shared
1891
+ listing is a native child of its request's Scope, started through
1892
+ `forkRequest` in the edge runner; an individual reader cannot terminate a read
1893
+ another reader still needs. Closing the request aborts and closes those children.
1894
+ Settled reads remove their request-close finalizer; active reads retain it,
1895
+ including in long-lived caller-supplied scopes.
1896
+
1897
+ Native catalog and maintained API entry points report policy, validation, and
1898
+ operation-construction throws as typed failures with the original thrown value,
1899
+ matching the Promise boundary. Native consumers can use `Effect.catch` and
1900
+ `Effect.result` without losing refusals to the defect channel. Interruption
1901
+ remains interruption. Normalization belongs at these program boundaries;
1902
+ pure catalog algorithms and core validation retain their existing behavior.
1903
+
1904
+ `RequestScope` in `src/runtime/request-scope.ts` represents the existing request
1905
+ identity. It owns call revocation, teardown, operation signals, the complete
1906
+ sent-secret set, and invocation/auth recovery facts. `openRequest` preserves
1907
+ its synchronous revocation bridge. Live context access refuses after closure;
1908
+ `recordedService` and the owner's `retained` facts remain available for
1909
+ settlement and redaction. Sharing a request identity grants no call authority
1910
+ to a spread copy of a context. Call deadlines, request lifetime, and the OAuth
1911
+ refresh coordinator remain distinct owners.
1912
+
1913
+ Maintained API programs register their original tool-handler identity through
1914
+ `src/runtime/call-entry.ts`. Invocation enters the native API validation and
1915
+ settlement pipeline, which composes a registered tool program directly; the
1916
+ Promise handler is its public shell. Replacement and custom handlers stay
1917
+ Promise leaves. Both paths retain argument validation, generic withheld
1918
+ handler diagnostics, secret redaction, call revocation, and core write-outcome
1919
+ settlement. Custom connector hooks, vault and cache adapters, Web Crypto,
1920
+ downstream OAuth, and the remote SDK retain their existing Promise boundaries.
1921
+ Cross-request call admission deliberately remains a Promise handoff so workerd
1922
+ resumes queued work in the request waiting for the permit.
1923
+
1924
+ The `Storage` service composes built-in `getNativeStorage` operations directly.
1925
+ Registry wrappers propagate that optional capability through principal,
1926
+ connector, subject, and result prefixes, including nested namespaces; list
1927
+ results strip only their own prefix. Custom stores keep the Promise fallback.
1928
+ These adapters add no retry, transaction, or storage encoding and preserve
1929
+ D1's single atomic batch and SQLite's native transaction. Native activity
1930
+ lookup remains available to the route and activity consumers.
1931
+
1932
+ ### Native executor hosts (contributor)
1933
+
1934
+ `src/runtime/executor.ts` registers private execution programs by executor or
1935
+ lease identity and the original `execute` method. `executorProgram` composes a
1936
+ registered implementation directly; custom or replaced methods remain Promise
1937
+ leaves. Public shells call their own implementation directly, allowing wrappers
1938
+ to delegate to a saved original without recursion. Native interruption retains
1939
+ the QuickJS log prefix; Promise leaves are observed even when a terminal budget
1940
+ race wins before their settlement. Redaction and write settlement remain in the
1941
+ existing execute pipeline, using its request identity and call capabilities.
1942
+ The executor seam creates no second request or call owner.
1943
+
1944
+ Both built-ins acquire their permits through native admission and return
1945
+ execute-once leases with synchronous, idempotent release. A queued standalone
1946
+ execution crosses `awaitExecutorLease`'s Promise handoff before host setup, so
1947
+ workerd resumes in the waiting request. `execute_code` retains its public
1948
+ `acquire()` handoff for that same reason. A cancelled queued handoff withdraws
1949
+ its waiter, and a late custom acquisition releases its abandoned lease.
1950
+
1951
+ The Worker SDK prepares source and dispatchers against an inert loader. The
1952
+ native host then acquires the actual Loader handle and RPC entrypoint with
1953
+ `Effect.acquireRelease`, awaits the native RPC Promise, and races the run with
1954
+ its lease's cancellation Deferred. A resource Scope owns entrypoint-before-
1955
+ Loader disposal on every exit, including never-settling evaluation. Release
1956
+ closes that Scope synchronously before returning the permit; a failed disposer
1957
+ cannot skip the next one. Forwarders lose their providers and pending waits at
1958
+ run end. Only native RPC output and the run's private host-failure map can
1959
+ supply failure provenance. Guest source isolation, intrinsic capture, import
1960
+ routing, codecs and the SDK's source-generation checks remain ordinary leaves.
1961
+ Both built-ins normalize provider leaf returns with `Promise.resolve`, retaining
1962
+ direct JavaScript callers' synchronous value tolerance and original host
1963
+ rejections. The published provider-function signature remains Promise-shaped.
1964
+
1965
+ QuickJS owns host-call fibers in each active run's Scope. Completion,
1966
+ interruption, cancellation, recycling and shutdown revoke provider lookup and
1967
+ interrupt those fibers; late SDK results and stale IPC cannot reply to another
1968
+ run. Releasing a lease during readiness or crash backoff also cancels the old
1969
+ run before its slot is handed back. Child retirement closes its resource Scope,
1970
+ sends SIGTERM, and escalates to SIGKILL after one second. Shutdown joins native
1971
+ completion Deferreds for all retirement tails. Crash backoff remains a Schedule.
1972
+ `forkOwned` in `src/runtime/run.ts` starts the host and startup-watchdog children;
1973
+ no executor starts a fiber directly. Child process events, IPC encoding and
1974
+ bounds, QuickJS VM/job pumping and guest code remain platform or pure leaves.
1975
+
1976
+ `test/executor-native.test.ts` exercises native dispatch and Worker disposal,
1977
+ never-settling evaluation/host calls, late rejection, execute-once ownership and
1978
+ original acquisition errors in workerd. `test/quickjs-pool.node.test.ts` covers
1979
+ readiness release, stale/malformed IPC, late host failures, backoff and bounded
1980
+ kill. Real Worker and QuickJS guest contracts, the HTTP Worker budget/queued
1981
+ request fixture and native-output suites supply runtime parity evidence.
1982
+
1983
+ ### Native SQL storage (contributor)
1984
+
1985
+ `src/storage/sql-native.ts` composes KV, activity schema upgrades, retention,
1986
+ and bounded SQL copies as Effects. `src/storage/d1-copy.ts` composes the
1987
+ Workers KV copy, source-bound resume claims, verification, cutover guard, and
1988
+ recovery. Public factories and copy functions retain their Promise contracts;
1989
+ custom Promise SQL drivers retain the fallback lift.
1990
+
1991
+ `src/storage/sql-driver.ts` constructs a public Effect 4.0.5 `SqlClient`
1992
+ service inside each operation. Reads execute SQL `Statement` Effects and writes
1993
+ execute their raw operations through that service, including its connection
1994
+ borrowing, statement-transformer context, and tracing semantics. Internal
1995
+ statement transformers see the current service; no client, statement, or
1996
+ Reactivity state is cached across operations or requests.
1997
+
1998
+ Atomic batches remain one native database operation: D1's public `batch`, or
1999
+ SQLite's synchronous `BEGIN IMMEDIATE` through `COMMIT`, with rollback on
2000
+ failure. Neither path retries a dispatched write. The D1 binding's
2001
+ `meta.changes` is retained for every statement, including zero; no `RETURNING`
2002
+ payload, extra metadata statement, or D1 transaction is needed. The dependency
2003
+ is the existing exact `effect` pin. Connecta does not adopt `@effect/sql-d1`:
2004
+ its 4.0.5 batch API drops metadata and its binding contract is wider than
2005
+ Connecta's published structural interface.
2006
+
2007
+ The D.2 architecture allowance was measured against `0e314247` using cached
2008
+ official `node:22-slim` (Node 22.23.4, zlib 1.3.1-e00f703), no network, and
2009
+ read-only bundle artifacts: `/d1` grew from 62,516 to 88,305 bytes gzip
2010
+ (+25,789); the Worker example grew from 729,847 to 756,375 (+26,528).
2011
+ Final typed-driver failure normalization measures 88,319 and 756,377 bytes
2012
+ on that same official runtime. Executing real services and statement operations requires those runtime
2013
+ semantics; the earlier identity-compiler facade did not provide them. The
2014
+ explicit allowance changes only `/d1`'s cap from 62,542 to 90,000 bytes and
2015
+ the Worker example's cap from 743,000 to 760,000. Historical baselines and
2016
+ all other caps stay fixed. Final integration must remeasure the combined
2017
+ implementation and satisfy the same compatibility and privacy contracts.
2018
+
2019
+ The platform connection capability contains no request state. D1 prepares in
2020
+ the current request and retains no prepared handles or promises. SQLite keeps
2021
+ its existing connection-owned prepared-statement cache; externally supplied
2022
+ databases remain caller-owned. Schema readiness is a boolean. A microtask
2023
+ boundary precedes dispatch so pending cancellation can stop physical execution;
2024
+ a SQLite batch never yields between transaction controls. Cancellation cannot
2025
+ undo an already-submitted D1 write and never causes replay.
2026
+
2027
+ Public SQL Statement operations create spans, so SQL tracing is disabled
2028
+ before executing them. Private `SqlError` values contain fixed wording with
2029
+ no driver cause or parameters; the original thrown value is restored only
2030
+ at the private SQL boundary. Operator sinks receive no new SQL diagnostics.
2031
+ The tracing contract is checked with a recording tracer and a mutation that
2032
+ removes the guard, which must fail. Existing import and copy errors retain
2033
+ their fixed, payload-free wording.
2034
+
2035
+ `src/runtime/native-storage.ts` supplies optional `getNativeStorage` and
2036
+ `getNativeActivity` lookups. An internal consumer may compose their Effects
2037
+ without starting another fiber; an absent lookup requires the existing custom
2038
+ Promise-store fallback. Registration normalizes operation-construction,
2039
+ validation, and decoding throws into typed failures with the original thrown
2040
+ value, matching that fallback. Interruption remains interruption. The lookup
2041
+ module imports no SQL or platform implementation and does not change the
2042
+ published storage interfaces.
2043
+
2044
+ The synchronous state-file parser, validation, file read, and one atomic
2045
+ SQLite import remain native synchronous leaves because `importStateFile` is a
2046
+ published synchronous API. `openSqlite` retains file modes, WAL, busy timeout,
2047
+ and database ownership. `test/sql-effect-contract.ts` runs native-path,
2048
+ affected-row, 50-claim, cancellation, and tracing checks on SQLite and native
2049
+ D1. `test/kv-to-d1.test.ts` also reuses stores across separate workerd requests;
2050
+ the existing SQL contracts prove rollback, database-clock TTL, encodings,
2051
+ activity upgrades, and copy recovery.
2052
+
2053
+ ### Internal provider HTTP failures (contributor)
2054
+
2055
+ The kit's `src/http/errors.ts` maps every `HttpFailure` tag through
2056
+ `toConnectorCallError`. Its completeness-checked tag vocabulary is also the
2057
+ typed-client boundary's guard. Vendors declare closed public value vocabularies
2058
+ for body facts that select a failure meaning. Before sent-secret value redaction,
2059
+ the kit extracts those facts from a token-only copy and retains only declared
2060
+ values; unknown reasons or codes contribute no classification fact and fall
2061
+ back to status rules. The captured facts stay in request-local identity metadata,
2062
+ outside the failure's enumerable data. Classification reads them with complete
2063
+ bounded facts and reviewed headers. Vendors also declare each numeric source in
2064
+ `numericFacts`: an exact body `path` or lowercase `header`, a review reason, and
2065
+ an optional `format` (`integer` by default, `seconds`, `epoch-seconds`, or
2066
+ `retry-after`). The kit reads only these sources from the response before value
2067
+ redaction. It accepts nonnegative whole numbers and strings of at most
2068
+ 128 characters that contain only ASCII digits after trimming surrounding
2069
+ whitespace; `retry-after` also accepts HTTP-dates. Integers are capped at
2070
+ `Number.MAX_SAFE_INTEGER`; delay formats yield milliseconds clamped to
2071
+ 0–86,400,000. The `retryAfterMs` numeric fact supplies the kit's standard hint
2072
+ and requires an explicit delay format (`seconds`, `epoch-seconds`, or `retry-after`).
2073
+ For example, a vendor declares `{ retryAfterMs: { header: "retry-after",
2074
+ format: "retry-after", review: "HTTP Retry-After delay for failure recovery." } }`.
2075
+ Classification receives only parsed numbers, never their original text or
2076
+ undeclared fields. These facts may select a code, retryability and bounded delay;
2077
+ they never supply wording or records. Only after the verdict is fixed does wording
2078
+ receive numbers read separately from the redacted response, facts extracted
2079
+ separately from the redacted body (eight list entries)
2080
+ and, where the operation's value-safety review permits it, that redacted body.
2081
+ Pre-redaction facts never supply message text, logs, or operator records.
2082
+ Non-2xx responses always take this capture path. A connection opts in with
2083
+ `inBandRefusals: true` only when its result hook reports in-band refusals on
2084
+ successful statuses; their declared facts come from the whole response.
2085
+ Required vendor meanings for 401/403/404 precede the other defaults; 409 has no
2086
+ shared conflict default.
2087
+ Stripe declares HTTP 409 as a conflict, alongside its fact-driven 429
2088
+ `lock_timeout` rule. Its reviewed `bodyFailures` opt-in applies status-only
2089
+ classification to an oversized 424 reply, preserving the dependency-failure
2090
+ meaning when the bounded body cannot be read. The original ceiling diagnostic
2091
+ and answered status remain; the core still decides write outcomes. It does not
2092
+ apply to 401 or alter Q8. A fixed read-phase fact distinguishes failed body
2093
+ reads from malformed JSON only for vendors declaring that wording; raw
2094
+ exceptions never enter diagnostics. Other providers retain their mapping and
2095
+ wording.
2096
+ A 421 is `unavailable` and retryable only when the transport's core
2097
+ positive-read classification proves that request a read, including its actual
2098
+ method and method-override headers. The agent chooses another call; the kit
2099
+ does not resend it. This override preserves the vendor's wording and changes
2100
+ only the code and retryability. A physical write retains its existing failure
2101
+ mapping.
2102
+
2103
+ The mapping records each request's own served provenance and raw 401 facts.
2104
+ `api()` and invocation retain the sole write-outcome decision in the core's
2105
+ `providerCallFailure`/`settleWrite`. A blocked resend reports the first
2106
+ reservation's answer even for a read-classified tool issuing a physical write.
2107
+ An accepted write's blocked retry retains its original HTTP status; a slot
2108
+ occupied by an auxiliary read instead names the separate write step needed.
2109
+ Structured resend advice reaches output only when the core permits it. Generic
2110
+ unknown writes retain the core's diagnostic wait; Q8 removes it. The mapping
2111
+ adds no Promise entry or lifetime owner. `provider-failure-sweep.test.ts`,
2112
+ `guarded-attempts.node.test.ts`, `call-services.test.ts` and the native
2113
+ transport suites exercise these contracts through invocation, physical sockets
2114
+ and request closure. `http-kit.test.ts` retains focused numeric/text fact,
2115
+ request-equivalence and bulk-capacity fault probes. The migration-era
2116
+ kit/core verdict comparison is retired under #1072. Maintained API providers use the kit through their
2117
+ explicit provider subpaths; it stays off the root import graph and published
2118
+ declarations expose Promises.
2119
+
1422
2120
  ### What stays plain
1423
2121
 
1424
2122
  - **Everything a deployment writes against.** The `Connector` contract,
@@ -1439,7 +2137,17 @@ storage is the root's namespaced to its principal.
1439
2137
 
1440
2138
  ### Across requests
1441
2139
 
1442
- Work shared across requests meets only through a Deferred that the owning
2140
+ Organization-mode Clerk Backend API reads share only plain membership verdicts,
2141
+ start times and sequences, active-call counters (16 per isolate, two per
2142
+ identity), and a bounded 429 backoff timestamp. They never share a promise,
2143
+ queue, signal, controller, or body across requests. A refused busy lookup is
2144
+ not cached. Each admitted read owns its ten-second deadline and cleanup;
2145
+ `finally` releases its counters after ending its upstream I/O, and the owning
2146
+ request's abort handler also releases them idempotently if workerd discards the
2147
+ pending continuation on disconnect. Membership cache
2148
+ hits precede these bounds; see [organization admission](./auth.md#one-clerk-instance-one-organization-per-deployment).
2149
+
2150
+ Asynchronous work shared across requests meets only through a Deferred that the owning
1443
2151
  request completes, never through a fiber that outlives its request. Two
1444
2152
  workerd rules follow, and both have bitten.
1445
2153
 
@@ -1481,7 +2189,714 @@ within five minutes of expiry in the same auth partition. Partial walks and
1481
2189
  auth or permission failures refuse fallback; resources require unexpired
1482
2190
  inventories, and stale fallback cannot authorize post-entry auth recovery.
1483
2191
 
1484
- ### Why not Schema or HttpApi
2192
+ ### Contributor provider kit
2193
+
2194
+ Provider declarations use `src/http/vendor.ts` for HttpApi operations and
2195
+ `src/http/tools.ts` for list, get, read and write builders.
2196
+ `src/http/provider.ts` composes them with `defineProvider`; it returns the
2197
+ ordinary Promise-based Connector factory. The kit stays outside the root
2198
+ graph and its Effect types are internal. In each provider, `api.ts` declares
2199
+ the vendor and `tools.ts` the tools.
2200
+
2201
+ Decision 0011's standard surface (Tithe.ly's step 2) adds four kit pieces.
2202
+ A `list` without the legacy `key` returns `{ items, page }`, with
2203
+ `page.nextCursor` only while `hasMore`, and the kit appends `limit` (the
2204
+ declared default and maximum), `cursor` and `fields` to the tool's filters;
2205
+ `read` and `get` take `fields: true`. `src/http/fields.ts` narrows after every
2206
+ projection. A standard list may opt into `total: true` and supply an optional
2207
+ exact vendor count as `page.total` from its page callback. The kit validates a
2208
+ nonnegative safe integer and includes it on continuing and terminal pages;
2209
+ without a vendor count the property is absent. Lists without the opt-in keep
2210
+ their existing schema and result bytes. Never derive an exact total from a
2211
+ partial page or an estimate. `src/http/fields.ts` refuses an unknown first
2212
+ segment before dispatch; a selected
2213
+ result renders against a sparse output schema. `src/http/hatch.ts` is the
2214
+ standard hatch (`_api_search`, `_api_details`, `_api_read`, `_api_write`) over
2215
+ an `OperationIndex` with explicit verdicts. It keeps the REST family's
2216
+ argument and envelope names and validates `fields` against the operation's
2217
+ declared response roots before dispatch. Search answers stay within 16 KiB
2218
+ (at most eight compact entries, long labels cut and marked, continuation by
2219
+ cursor); details stay within 32 KiB, paging whole parameters and body
2220
+ properties, and chunking any piece larger than one answer, behind a bound
2221
+ cursor. Standard hatch search removes conversational framing; framing-only
2222
+ queries return no matches. It admits partial lexical matches alongside full
2223
+ matches. Action/read queries require all content nouns in the operation text,
2224
+ or any noun in the path/id with at least half the nouns in the text. Their
2225
+ field-only matches need half-noun coverage across text and declared field
2226
+ names. Verb-less noun phrases retain their broader rule: any noun in the
2227
+ path/id, or all nouns in the text. A leading create/add/update/edit/make/set/apply/change/rename/send/delete/remove/cancel/stop
2228
+ with an object sets write intent and retains later read words as nouns. Make,
2229
+ set, apply, change, edit and rename share update synonyms. `make a`,
2230
+ `make an`, `make new` and `set up` phrases instead select create synonyms,
2231
+ including after leading conversational framing such as `please` or `i want to`;
2232
+ `make text bold` retains update intent. Otherwise, a read
2233
+ word anywhere sets read intent, including after refund/charge/issue/process.
2234
+ Short function words and id-shaped tokens do not contribute to noun coverage.
2235
+ Sheets A1-style cell references also supply no resource noun. A write query
2236
+ whose supplied object contains no resource nouns includes its verb synonyms
2237
+ in candidate retrieval, so values do not restrict it to reads mentioning them.
2238
+ Verb-only queries and a rename destination without an object retain ordinary
2239
+ retrieval.
2240
+ Reviewed intent, the requested verb/synonym in summary/id/path, and resource
2241
+ focus sort first. A refused operation with a matching verb shares the intent
2242
+ tier only when its noun coverage exceeds every admitted operation matching that
2243
+ verb and intent. Search and details retain the original indexed summary and expose the reviewed
2244
+ reason in a separate optional `refusal` field. Ranking uses the original indexed
2245
+ text, never the newly attached reason. Search caps refusal text at 512 UTF-16
2246
+ code units without splitting a surrogate pair, independently of its 300-unit
2247
+ summary cap. A cut reason names `operations[i].refusal` in `truncated`; details
2248
+ returns the complete reason within its 32 KiB envelope or reconstructible
2249
+ `of: "refusal"` text chunks. The five long Cloudflare reasons fit whole in the
2250
+ search refusal budget, including their named alternatives. REST safety tables
2251
+ supply the reason through `restSafety`; reviewed bindings declare static operation
2252
+ refusals separately from argument validation. Discovery cursors additionally bind
2253
+ the summary/refusal representation format, so an old piece offset cannot skip a
2254
+ required parameter after this representation changes. The binding's deployment
2255
+ `cursorScope`, named suggestions and paging totals retain their existing seams.
2256
+ An unreviewed operation reports the missing hatch verdict. A refused hit has
2257
+ no read/write hatch pointer; a reviewed named alternative can still be shown.
2258
+
2259
+ Verb-less noun phrases first sort by noun coverage and an exact summary phrase
2260
+ prefix, then by an exact terminal resource: the last literal non-version segment must contain the last query noun
2261
+ or the concatenated noun phrase (`invoiceitems` for `invoice items`). An authored
2262
+ read whose vendor summary/id covers every noun shares that terminal-resource
2263
+ tier only when a real terminal-resource match exists and its description
2264
+ begins with a read action; the action word itself
2265
+ does not supply noun coverage. This preserves aggregate
2266
+ authored entry points without treating a verification action as a token list. When both candidates match the terminal resource,
2267
+ a reviewed read precedes writes and refusals. Depth with a one-segment authored
2268
+ tool preference, exact resource-noun coverage, and authored-tool preference
2269
+ break remaining ties. A pointer to another hatch receives no authored preference.
2270
+ Exact resource nouns include literal namespaces on either side of a version
2271
+ segment; parameters and version markers supply no resource coverage. This
2272
+ prefers `members` over summary-only membership matches and `tokens` over a
2273
+ verification action, while direct versioned collections retain their depth
2274
+ advantage over nested collections. For a single noun, and for
2275
+ leading `list` queries with one exact resource noun plus modifiers, collection
2276
+ ties sort by exact root, fewer nested resources, matching namespace, shallow
2277
+ depth and authored tool. Other leading `list` queries prefer authored tools
2278
+ whose summary/id covers every noun. This retains descriptive aliases while
2279
+ restoring [decision 0014](https://github.com/zackbart/connecta/blob/main/decisions/0014-api-tool-shape.md)'s
2280
+ noun-phrase preference. Person/people and mail/email aliases, s/es/ies plurals and hyphenated list
2281
+ roots share the collection keys. Sort keys are computed once per operation
2282
+ per query; lexical relevance and index order resolve remaining ties.
2283
+ Operations found only through a request or response field follow primary
2284
+ matches, and search names an authored tool whenever one serves an operation.
2285
+ A refused method outside a read/write tool's input schema is inspected through
2286
+ `_api_details` with its indexed method and path. Call validation remains closed:
2287
+ Gmail's 49 non-GET refusals do not add a method field or a write hatch, and Forms'
2288
+ refused DELETE remains outside its write enum. GitHub's refused raw PUT merge
2289
+ is likewise outside its write enum; its named guarded alternative is unaffected.
2290
+ Such schema refusals happen
2291
+ before authentication or I/O; details exposes the same reviewed reason as search.
2292
+ Every schema-reachable refused operation still fails with that reason before
2293
+ authentication or dispatch. This is policy inspection, never execution admission.
2294
+ `_api_read` omits
2295
+ `method` only when GET is its one reviewed read method.
2296
+ A standard list can declare `limit.values` for a vendor's discrete page sizes.
2297
+ The values must be unique positive integers within `max` and contain the default.
2298
+ The catalog declares the enum and decoding refuses other sizes before dispatch.
2299
+ A reviewed read-only POST list supplies `review`, like other reviewed POST reads.
2300
+ A reviewed OpenAPI pin may opt into `additionalResponseMediaTypes` to declare
2301
+ response fields from a vendor's nonstandard media labels. This changes field
2302
+ metadata only; the runtime still enforces its declared response format.
2303
+
2304
+ A provider may opt into `hatch.writeEnabled(options)` to omit its write hatch
2305
+ from a connection whose deployment configuration permits only reads. Discovery
2306
+ omits write-tool routing on that connection; reviewed reads do not gain write
2307
+ verdicts. The default remains enabled, and endpoint read-only policy still
2308
+ enforces every invocation.
2309
+
2310
+ The hatch's optional `pages` table declares an adapter for each indexed read
2311
+ that pages. `src/http/hatch-paging.ts` supplies Stripe forward lists
2312
+ (`has_more`, last `data` id → `starting_after`), Cloudflare
2313
+ (`result_info.page/total_pages`, falling back to `total_count/per_page/count`
2314
+ → `page`), Vercel (`pagination.next` → `until`), and GitHub (quoted or unquoted
2315
+ Link `rel=next` → `page`). Stripe opts into indexed forward lists, reverse
2316
+ lists (`ending_before`, first safe row id), v1 search (`next_page`) and v2
2317
+ lists (a confined `next_page_url` supplying only `page`).
2318
+ If a Stripe v2 URL adds query parameters the original call omitted, the read
2319
+ keeps its data and returns `hasMore: true` without `nextCursor`; a continuation
2320
+ cannot be minted without guessing which vendor defaults to preserve. Changed
2321
+ explicit filters and URLs outside the confined origin/path remain refused.
2322
+ A declaration names the cursor parameter and `query` or `body`; it must
2323
+ exist in that operation's request contract. The response remains
2324
+ `{ status, data, page? }`, with `page: { hasMore, nextCursor?, param, in }`.
2325
+ With parameter-based continuation, put the opaque `nextCursor` back in the named
2326
+ parameter. The hatch binds it
2327
+ to its tool, content-hash index revision, method, path, other arguments and selected fields,
2328
+ canonicalizing nested object key order while preserving array order and scalar types.
2329
+ It decodes the cursor before index validation and sends only the vendor value. Paging is
2330
+ read after connection safety and before field selection; result hooks must
2331
+ retain the paging metadata. Only an adapter's explicitly requested response
2332
+ headers enter its answer, already sent-secret redacted. GitHub extracts just
2333
+ the page from a Link URL with the same origin and unchanged filters, including
2334
+ GitHub's repository-ID paths; it never follows the URL or changes the original
2335
+ request path. Filter comparison uses parsed scalar parameter multisets,
2336
+ preserving duplicate counts and tolerating order and URL-encoding differences.
2337
+ Adapter continuations use the same root-store HMAC key and admitted
2338
+ connection/personal-owner binding as discovery cursors. The signed v2 token's
2339
+ claims preserve the vendor value's string/number type and pass the request-secret
2340
+ encoder. An unpaged hatch keeps its existing result schema.
2341
+ The optional `pageCursor: "argument"` declaration separates a top-level
2342
+ signed `cursor` from raw first-request anchors. Stripe allows a raw
2343
+ `starting_after` or `ending_before` record id in `query` only without cursor;
2344
+ both directions or a cursor with any raw anchor are refused before dispatch.
2345
+ Returned tokens authenticate the selected adapter parameter and value as well
2346
+ as the call. Search/v2 raw page tokens cannot replace cursor. Providers without
2347
+ this declaration keep parameter-based continuation.
2348
+ An explicit initialAnchor adapter on parameter-based paging can retain a numeric
2349
+ timestamp as a first-page time-window anchor; opaque string continuations still
2350
+ require a signed token.
2351
+ The generated Paging convention follows the read tool's input schema: tools
2352
+ that take `cursor` restore `page.nextCursor` as that top-level argument; tools
2353
+ without it restore the token in `query[page.param]` or `body[page.param]`, as
2354
+ `page.in` specifies. `kit.hatch.paging` still validates declared mode values as
2355
+ a sanity check; it does not select the generated continuation instruction.
2356
+ An unsafe continuation keeps the safe page data with `hasMore: true` and no
2357
+ `nextCursor`.
2358
+ A reviewed `HatchBinding` can supply vendor encoding/origin routing through
2359
+ the same Connection API; the shared hatch retains resolution, verdicts,
2360
+ index/fields validation, withheld-value admission, paging and projection.
2361
+ A binding may resolve configured query routing before validation and signed
2362
+ cursor binding. Vercel uses this to apply its teamId default and deliberate
2363
+ teamId/slug/null overrides consistently with named tools and uploads.
2364
+ A binding may opt into `truncated: true` to supply reviewed size-cut paths
2365
+ (such as `data.content`) in its answer. The hatch then declares and propagates
2366
+ an optional top-level `truncated: string[]` on reads and writes. Without the
2367
+ opt-in, the schemas and results are unchanged; size-cut metadata is not inferred
2368
+ from vendor booleans or added to other providers. A separate `warnings: true`
2369
+ opt-in declares and preserves reviewed `warnings: string[]` beside `data`, even
2370
+ when `fields` narrows the vendor body.
2371
+
2372
+ A binding may expand deployment-owned path defaults (Cloudflare) or canonicalize
2373
+ repository contents paths (GitHub) before indexed resolution, and may prepare a
2374
+ reviewed text/NDJSON body before validation. Both the original
2375
+ and resolved paths pass withheld-value admission. Closed request-header enums
2376
+ are opt-in; selected headers join the paging cursor's call identity. Text body
2377
+ inputs are opt-in too. A provider with bounded call-owned scope lookups declares
2378
+ its fixed write steps, preserving the resource call's single-attempt slot.
2379
+ A page adapter may declare `cursorInputMessage` to give provider-specific repair
2380
+ guidance when an unsigned raw value is supplied; absent keeps the standard refusal.
2381
+ An optional `fieldsRoot` supplies vendor-envelope selection guidance and the
2382
+ corresponding generated convention; it never changes the declared response roots.
2383
+ Field selection on every provider, including named reads, accepts optional `[]`
2384
+ array notation (`result.id` and `result[].id` are equivalent); this normalization
2385
+ is shared behavior, independent of `fieldsRoot`.
2386
+ Bindings retain the hatch's other-server and JSON/form body-type guards by
2387
+ default. `allowOtherServers: true` and `allowOtherBodyTypes: true` explicitly
2388
+ replace those guards with the binding's reviewed routing or encoding checks.
2389
+ Stripe uses it for nested v1 form parameters, v2 JSON, account/context headers
2390
+ and the confined PDF origin. An optional hatch idempotency declaration uses
2391
+ the kit write builder's checked key hook: Stripe POST writes take or generate
2392
+ keys, while DELETE and the read-only invoice-preview POST send none.
2393
+ Redacted or withheld cursor values are refused both before minting a continuation
2394
+ and after restoring a decoded continuation, before dispatch. The shared
2395
+ `refuseWithheldValues` check runs on the restored query/body: paths and queries
2396
+ refuse marker containment, while bodies retain the whole-value/link rule.
2397
+ `src/http/vendor-index.ts` derives that index from a kit vendor's own
2398
+ declarations when the vendor has no pinned OpenAPI document: `form()` request
2399
+ contracts, `Summary` annotations and lenient success schemas become the
2400
+ index's parameters, bodies, summaries and declared fields (optional
2401
+ `OpenApiData.fields`). Kit facts carry the vendor's declared
2402
+ `VendorErrors.meanings` and an `idempotencyKey` checked against the write
2403
+ builders at construction, which the generated Conventions state.
2404
+
2405
+ Hatch request paths and query keys/values refuse any containment of the shared
2406
+ `[redacted]` marker, including decoded spellings. Body fields and kit write
2407
+ arguments refuse a string that equals the marker after decoding, or carries a
2408
+ withheld link (including origin-only links ending in the marker). Longer free
2409
+ text such as `the log says: Authorization: Bearer [redacted]` is accepted.
2410
+ Admission iteratively visits keys and values before the write program runs;
2411
+ it checks agent arguments, not whole bodies later composed from internal
2412
+ reads. The kit connection also checks actual typed path parameters and query
2413
+ values before transport. Legacy non-kit tools retain their link-only admission;
2414
+ arbitrary binary/base64 bytes are not decoded by this scalar rule. The kit
2415
+ sets `refusesWithheld` for the standard hatch, enabling the generated convention.
2416
+ Declared error-code facts use the closed `ConnectorCallErrorCode` set at
2417
+ construction (INV-11); classification callbacks are not run at construction.
2418
+
2419
+ Bound cursors MAC a canonical tuple of version, admitted connection id,
2420
+ personal registry's credential-owner partition (null for shared registries),
2421
+ canonical tool/argument digest and continuation payload with HMAC-SHA-256.
2422
+ The canonical envelope spelling is required too. Hatch discovery binds a
2423
+ SHA-256 content hash of the index (operations, details, fields and routing
2424
+ metadata), alongside its declared label; the displayed revision label remains
2425
+ unchanged. Index edits invalidate old continuations even without a version
2426
+ bump. Search offsets, details pieces/chunks and surrogate boundaries are
2427
+ validated. Tampering or a changed scope/index fails `invalid_args`; changing
2428
+ only `limit` remains allowed.
2429
+
2430
+ One random 256-bit signing key is initialized lazily with compare-and-set
2431
+ in the durable root `continuationKeys` family. Only resolved, nonextractable
2432
+ keys are cached per store; no request promise or key value is logged. Registry
2433
+ `rootStorage` preserves the explicit deployment root across personal partitions
2434
+ independently of `catalogStorage`; standalone API connectors bind their own id
2435
+ and use their supplied store. Every deployment resolves a store, so no unsigned
2436
+ fallback exists. SQL-backed cursors survive isolate/process changes; memory
2437
+ storage loses them with the store. Key-load outages or corrupt stored keys
2438
+ produce retryable `unavailable`, naming the cursor signing key without its
2439
+ value. See [cursor-key recovery](./deploying.md#recover-a-cursor-signing-key):
2440
+ after repairing/deleting a corrupt key, restart every process/isolate to clear
2441
+ cached keys. Pre-upgrade cursors require a new first page.
2442
+
2443
+ The helper derives connection metadata and probe messages from
2444
+ validated options and still accepts complete legacy usage guides for compatibility.
2445
+ All maintained providers have completed the standard-surface migration.
2446
+ Candidate checks can refuse before authentication while
2447
+ preserving the provider's exact diagnostic. Its optional `authScope(options)`
2448
+ selector fixes OAuth grant ownership from deployment mode at construction;
2449
+ CCB uses shared System Auth grants and personal Identity Auth grants. Tool
2450
+ arguments cannot select a grant's owner. On Node, every maintained-provider
2451
+ request uses the owned sender `listen()` installs, reads included. Without it,
2452
+ maintained-provider writes and reviewed POST reads are refused before sending;
2453
+ focused suites exercising those requests configure the sender themselves. Anything a connection
2454
+ caches across calls, such as its typed HttpApi client, is built with no
2455
+ services, because Effect constructors close over the context they run in and a
2456
+ cached value would otherwise keep its first caller's request alive (INV-7).
2457
+
2458
+ The optional `grant(options)` factory supplies a renewable machine-login
2459
+ `Grant`, exclusive with static OAuth. It is built outside caller contexts;
2460
+ each send binds the active call to its token, refresh and rejected effects.
2461
+ `settleWrite` decides the outcome and handoff.
2462
+ Reads replay once after renewal; writes are never resent.
2463
+
2464
+ A machine grant may also declare `grantRenewal` modes, with an optional
2465
+ `Grant.discard(token)` that forgets exactly that refused token and keeps any
2466
+ newer one. `replayRejection: "answer"` discards the token a replayed read was
2467
+ refused with and hands that 401 to the vendor's status table, so the message
2468
+ comes from the bounded, sent-secret-redacted body instead of `rejected`.
2469
+ `writeRenewal: "discard"` discards a write's refused token without renewing:
2470
+ no token request is sent, nothing is recorded as renewed, the write's 401 is
2471
+ answered, and `settleWrite` decides its outcome and handoff. Neither mode
2472
+ resends a write, and the defaults are unchanged. Google Workspace uses both,
2473
+ because its delegated tokens are minted per call and a refused write only
2474
+ needs the next call to mint again.
2475
+
2476
+ All Google Workspace products use `src/providers/_shared/google/kit.ts`, which
2477
+ puts the delegated service account on a machine grant. `workspace.ts` owns
2478
+ shared options, connection parsing and subject resolution; `service-account.ts`
2479
+ owns token exchange and caching. JSON requests use `googleClient`, and
2480
+ Drive's `driveClient` adds binary and charset-aware text projections over
2481
+ that adapter. The subject resolves once per call from deployment config and
2482
+ the admitted caller, and the private token exchange keeps its in-memory
2483
+ cache, so only token strings and expiries cross requests. Each product
2484
+ declares its operations, and a value-safety resolver refuses any path it does
2485
+ not declare. Revision-guarded writes set the
2486
+ `revisionGuarded` request flag that Google's error table reads. The completed
2487
+ product migrations use Effect-declared named tools and indexed
2488
+ hatches. Fixed write steps are snapshotted at construction. Docs declares a
2489
+ create and its optional initial-text edit as separate steps, each sent at most
2490
+ once.
2491
+
2492
+ All six Google Workspace products use the shared standard-surface support.
2493
+ `scripts/google-discovery.mjs` converts
2494
+ archived exact-byte official Discovery documents into deterministic OpenAPI.
2495
+ The primary vendored pin retains the official URL, revision and source digest;
2496
+ its `discovery` relation names the sibling source pin and normalizer format.
2497
+ The sole retained document is `evidence/discovery.document.json`; no converted
2498
+ `openapi.document.json` is retained for these pins. Generation and offline
2499
+ freshness validate the original bytes against `discovery.json` and the primary
2500
+ pin, then derive the conversion in memory, including reviewed summary overrides.
2501
+ Its digest uses two-space JSON indentation and a terminal newline; the generated
2502
+ module records that independent document digest, preserving freshness checks
2503
+ for changes to the normalizer or summaries. This supersedes Decision 0011's
2504
+ retained-conversion requirement for deterministic Discovery evidence. Frozen
2505
+ measurements read those same provider-owned originals but validate the existing
2506
+ `test/fixtures/google-discovery/manifest.json` digests independently: a provider
2507
+ pin update must fail the historical measurement rather than silently rebase it.
2508
+ That reviewed pin-change PR must re-measure and update the product's manifest
2509
+ entry and the Forms #1009 drift-check digests when applicable, or retire the
2510
+ measurement. Full source method inventory
2511
+ and canonical source-to-relative-path mappings survive normalization. Gmail's
2512
+ source `userId` becomes deployment-owned `me`; runtime paths are relative to
2513
+ the existing connection root. Reserved Slides ids remain one encoded segment.
2514
+ Flat-path aliases are reconciled with the declared path parameters. Method-
2515
+ specific required annotations specialize only affected reachable input schemas,
2516
+ including nested and recursive references. Deprecation metadata is retained;
2517
+ compact details opt in with `deprecations: true`. Live Discovery drift compares
2518
+ sorted-key JSON and revision, independently of archived-byte integrity.
2519
+ Only safe JSON query globals enter the contract. Source bytes are contributor
2520
+ evidence and never runtime imports.
2521
+
2522
+ An index can retain `requestSchemas`, a lazy referenced graph for complete
2523
+ request details, independently of the compact depth-bounded admission checker.
2524
+ Details attach the reachable `$defs`, preserving recursion, required fields,
2525
+ dynamic maps and parent schema metadata through the existing bounded
2526
+ continuations. This does not make the compact checker a full JSON Schema
2527
+ validator. Product admission still needs literal reviewed verdicts and
2528
+ independent batch-kind, revision and resource guards. Google indexes explicitly
2529
+ set `pathMatching: "uri-template"` on `OperationIndex` to handle encoded literal
2530
+ collisions and parameter action suffixes. Other indexes retain exact literal
2531
+ segments and their existing refusal ordering. The matching mode also binds
2532
+ discovery cursors. `googleClient(connection, index)` requires each named request's
2533
+ declared `template` and refuses a substituted id resolving to another operation
2534
+ before grant evaluation. Every maintained Google product uses this indexed
2535
+ client; there is no unindexed settlement mode.
2536
+ Both the client and `googleHatchBinding(connection, { index, review })` require
2537
+ the explicit URI-template mode at construction. The hatch requires the binding's
2538
+ exact index identity. Clients and Google bindings reject a missing
2539
+ index with an explicit construction error. The provider-standard suite checks
2540
+ actual hatch construction through every Google product fixture, requiring its
2541
+ Google binding with the same connection and index. The Google binding
2542
+ additionally refuses percent-encoded unreserved characters and colons before
2543
+ dispatch.
2544
+
2545
+ The generator's `allSuccessResponses: true` option unions JSON response fields
2546
+ from every 2xx. Existing providers keep the first JSON success contract until
2547
+ their own reviewed regeneration; no committed provider index changes here.
2548
+
2549
+ `src/providers/_shared/google/hatch.ts` supplies JSON framing over the existing
2550
+ confined `Connection.sendPath`. It retains the hatch's origin/media guards,
2551
+ delegated grant, safety and field-selection pipeline. Its optional `wrapSend`
2552
+ receives a detached copy of the admitted request (including its body) and a
2553
+ zero-argument inner send. The binding retains its original `send` function;
2554
+ wrappers run only after hatch admission and can dispatch the captured request
2555
+ at most once. A second send is an internal defect; after a write dispatch its
2556
+ outcome remains unknown. Opting into this wrapper also enables envelope-level
2557
+ warnings, so request-dependent save advice survives field selection without
2558
+ changing vendor data. Mutating the inspection copy cannot change its path, body,
2559
+ query, revision flag or safety policy. Products use this seam for response
2560
+ semantics that depend on the request, such as independent comment saves.
2561
+ The `googlePageToken`
2562
+ adapter is separately opt-in by exact list key with `pageCursor: "argument"`.
2563
+ It treats missing/empty `nextPageToken` as terminal, rejects malformed tokens,
2564
+ and continues empty pages when a nonempty token exists. `query.fields` is a
2565
+ Google vendor mask; the adapter preserves `nextPageToken` on the wire. The
2566
+ token must be selected as a complete top-level field; `nextPageToken/x` does
2567
+ not select it. The top-level `fields` argument narrows safe result data only after paging. Omit
2568
+ `pageToken` initially and restore the signed `page.nextCursor` as `cursor`;
2569
+ the raw token in safe vendor data is not accepted as continuation input.
2570
+ For example, a direct value-mode result has outer `result.data`, the hatch's
2571
+ `result.data.page.nextCursor`, and vendor JSON at `result.data.data`:
2572
+ `call_tool({ address: "crm.forms_api_read", args: { path:
2573
+ "/forms/form_1/responses", cursor: previous.data.page.nextCursor },
2574
+ resultMode: "value" })`. This is an envelope illustration for an opted-in
2575
+ provider, not a claim that the Forms migration has shipped.
2576
+
2577
+ `googleErrors(product, { standard: true })` declares the product's 404 meaning
2578
+ and uses common status fallbacks; revision-conflict meanings require a
2579
+ separate declaration. `googleClient` preserves failure phase
2580
+ metadata for partial-create recovery while leaving settlement and wording to
2581
+ the kit. Its incomplete-body wording covers JSON and binary reads without
2582
+ claiming that binary success was expected to be JSON. `googleGrant` still uses
2583
+ the nonstandard `googleErrors(product)` mapping for delegated-token rejection;
2584
+ that grant contract is separate from resource-request settlement.
2585
+
2586
+ Drive's two-request reads also declare fixed steps: `get_file_content` uses
2587
+ `metadata` and `content`, and non-root `list_folder_items` uses `folder` and
2588
+ `files`. These reads still send both GETs when deployment classification
2589
+ serves them as writes; each step can leave only once.
2590
+ Gmail declares separate initial and body-download steps for `get_message`,
2591
+ `get_draft`, and `get_attachment`; `search_threads` and `list_drafts` name
2592
+ each of up to 25 summary reads, and `get_thread` names each stored body in
2593
+ Gmail's 100-message conversation limit. Serving these reads as writes keeps
2594
+ every request available once.
2595
+ The request adapter reports how far a failed request got; the kit maps the
2596
+ failure and the core decides every write's outcome. Gmail's auxiliary draft and reply lookups pass both `effect: "read"`
2597
+ and a construction-declared step: the guarded client's early reservation uses
2598
+ that named slot, while the ledger marks the request auxiliary. A failed lookup
2599
+ keeps its read verdict and cannot make an unsent draft write unknown.
2600
+
2601
+ Sheets declares its seven API methods in `src/providers/sheets/api.ts`; its
2602
+ kit tools retain the private digest-bound cell cursor, legacy A1 path encoding,
2603
+ and create/append recovery advice. Its projections and the Google failure
2604
+ classifier read fields by the names Google gave them, which the kit's parse pass
2605
+ keeps. Its catalog and guide stay unchanged in step 1.
2606
+
2607
+ Google's request adapter also offers a reviewed binary send. It marks the
2608
+ operation `googleBytes` for the product's `binaryBodies` selector and forwards
2609
+ an optional `maxBytes` to the success-only prefix reader. It returns the
2610
+ connection's safe answer: bytes pass the existing sent-secret encoding before
2611
+ any result hook, then value safety before the product reads them. Products
2612
+ keep charset decoding and special error-status interpretation local.
2613
+
2614
+ An optional `tagConnector(connector, options)` records construction facts
2615
+ about the built connector, once, before the registry can serve it. Its return
2616
+ value is ignored, so it cannot replace or wrap the connector. Google Workspace
2617
+ uses it to tag whose account the connector acts as, which the registry reads to
2618
+ hide a mapping-subject connector from agents in a pool without an owner.
2619
+
2620
+ GitHub's tools and allowlisted indexed hatch use kit read/write builders and a confined
2621
+ Connection while its App authentication and scope-verification transport stays
2622
+ private. Its tokens are partitioned by App/key, installation, repository IDs,
2623
+ permissions and purpose. A resource 401, or a 403 without rate-limit evidence,
2624
+ invalidates the exact bearer even when its body cannot be read. The next call
2625
+ mints lazily; there is no inline renewal or replay and no renewable Grant.
2626
+ Only completed scalar cache data crosses requests; private signing imports
2627
+ and all transport operations remain request-owned.
2628
+
2629
+ The optional `probe.run(connection, options)` program replaces the operation
2630
+ probe when authentication needs a different read or a private login transport.
2631
+ It is built once at provider construction outside caller contexts, using the
2632
+ confined Connection and validated options. Execution uses the same anonymous,
2633
+ uncharged candidate services, accounting, settlement, failure mapping and
2634
+ finalizer. An empty write-capacity map refuses Connection writes before dispatch.
2635
+ Every defect from a run probe or its hooks uses the generic withheld notice,
2636
+ even without a kit dispatch, because a private transport leaves no kit visibility
2637
+ evidence. An open operation probe retains its original defect.
2638
+
2639
+ `src/providers/_shared/rest/kit.ts` owns the shared REST call shapes, pinned
2640
+ schema conversion and indexed vendor declarations. `indexedVendor` derives operation
2641
+ metadata from the index and builds the full manifest lazily; the typed client
2642
+ contains only named operations and probes. Bulk indexed declarations preserve
2643
+ the OpenAPI brace paths verbatim and carry explicit parameter Schemas. They
2644
+ serve the manifest only: typed sends refuse an undeclared operation name,
2645
+ and indexed hatches dispatch through `sendPath`. Parameter names outside
2646
+ `[A-Za-z0-9_-]+` fail construction. Manifest/index operation-set equality is
2647
+ checked independently of drift digests, which still use full vendor contracts.
2648
+
2649
+ A tool may opt into `inputOrder: "original"` when legacy request bytes depend
2650
+ on argument insertion order. After normal successful decoding, the builder
2651
+ copies only decoded values in the original key order, recursively through
2652
+ objects and arrays. Dropped keys stay dropped and decoder-introduced keys
2653
+ append at the end. Capacity selectors, idempotency and the tool program all
2654
+ receive that reordered decoded value. The default preserves the decoder's
2655
+ existing order; the option retains no original values and skips no validation.
2656
+
2657
+ The kit write builder emits `readOnlyHint:false` and its explicit `destructive`
2658
+ verdict as `destructiveHint`; `destructive:false` describes an additive write.
2659
+ A reviewed `idempotent` declaration emits `idempotentHint` independently of a
2660
+ transport idempotency key. These hints describe behavior; write routing and
2661
+ endpoint restrictions still apply. For example, a tool that only creates a new
2662
+ note can declare `destructive:false` without promising idempotency.
2663
+
2664
+ Kit tools and the indexed hatch may declare a `writeSteps` map whose values are `"once"`.
2665
+ Construction freezes the map; every named step sends at most once and an
2666
+ undeclared step is refused before dispatch. Omitting it retains the single
2667
+ implicit write step. Auxiliary ownership reads in a write call retain that
2668
+ call's write classification and consume their declared lookup steps, so the
2669
+ resource write cannot be resent.
2670
+
2671
+ Reviewed response policies can select text using only the redacted, normalized
2672
+ Content-Type. A separate `binaryBodies` review opts into bounded byte reads.
2673
+ Only the connection sees those bytes: it calls `RequestSecrets.encode` before
2674
+ constructing the legacy `{ contentType, bytes, base64 }` value. Undeclared
2675
+ binary bodies retain the JSON-only failure. Fixed secondary origins use a
2676
+ separate kit Connection built during provider construction, with their own
2677
+ confinement and the same authentication and result safety.
2678
+
2679
+ A definition may derive authentication and default admission from validated
2680
+ options. Probes may derive their request from those options and describe a mapped
2681
+ failure (for example, an accepted key lacking account-read permission). Public
2682
+ routing/version headers remain request or constant headers, never authentication
2683
+ secrets. An optional positive `timeoutMs` retains a vendor's request and body
2684
+ deadline. A path request opting into `emptyForm: true` retains an explicitly empty form on the wire; other empty form declarations keep their previous omission.
2685
+
2686
+ `PathRequest.rawBody` accepts a string, `Uint8Array` or `FormData`; it is
2687
+ exclusive with JSON, form, `emptyForm` and item/bulk identities. Conflicting
2688
+ combinations fail construction where possible and otherwise refuse before
2689
+ dispatch. Framing uses Effect's text, byte-array or form-data body helpers.
2690
+ An explicit Content-Type is preserved exactly; multipart callers omit it so
2691
+ fetch generates its boundary. This field does not change classification,
2692
+ write-step accounting or the sent-secret set, and existing size ceilings apply.
2693
+
2694
+ A send may opt into a success-only `prefix` read with a byte ceiling, an optional
2695
+ tighter time limit and a boolean chunk-stop callback. Failure responses retain
2696
+ the ordinary bounded status and withheld-error path. Early success cancels the
2697
+ unread stream and reports `prefix: { truncated, bytes, stopped }`; accepted bytes
2698
+ still pass sent-secret redaction, the result hook and value safety before any
2699
+ projection. The callback runs with an empty Effect context, receives a copy of
2700
+ the chunk, and cannot transform the answer. Its defects receive the same
2701
+ withheld-text rewrap as result hooks. Invocation cancellation remains authoritative.
2702
+ Reviewed binary prefixes retain bytes through the same sent-secret encoding
2703
+ as whole binary bodies, without a text decode. Only that binary path accepts
2704
+ a zero-byte prefix, which keeps empty bytes and the ordinary prefix facts.
2705
+
2706
+ CCB's static OAuth bridge opts into `oauthWrite401: "grant"`. An optional
2707
+ `refreshOnce` mapper receives only renewal facts and reuses the core's
2708
+ `unsentAfterUnauthorized` diagnostic, preserving the grant's authorization
2709
+ message and the message for a renewed credential whose write was not replayed.
2710
+ The error retains the resource dispatch's provenance; `settleWrite` decides
2711
+ the final outcome. The default middleware behavior and renewal ownership stay
2712
+ unchanged.
2713
+
2714
+ The static compatibility option caps discarded 401 challenges with the shared
2715
+ OAuth challenge limit before renewal. A guarded client's optional
2716
+ `maxUnauthorizedResponseBytes` applies to declared and streamed bodies;
2717
+ `read(response, maxBytes)` can narrow a read's ceiling further. The middleware
2718
+ maps an oversized challenge using its own recorded dispatch, including a
2719
+ declared-size refusal before the response wrapper exists. It never renews
2720
+ after that refusal.
2721
+
2722
+ The kit's parse pass redacts every value of a parsed JSON body and keeps its
2723
+ object keys. Value safety drops and redacts fields, and projections navigate
2724
+ them, by the names the vendor gave them, so a private argument sharing part of
2725
+ a name such as `client_secret` or `paragraph` cannot hide the field from the
2726
+ rule that withholds it or erase the structure a projection reads. A result
2727
+ hook that redacts again does so the same way. Text, binary bodies and JSON
2728
+ embedded inside a string are values and retain ordinary redaction. Response
2729
+ headers keep their names, which `Headers` must accept, and their values are
2730
+ redacted together. Keys are checked once value safety and the projections have
2731
+ run: the `api()` boundary and the invocation apply payload rules to the
2732
+ connector's result and failures, keys included, before the agent boundary's
2733
+ pass. Until then a key is the vendor's text, so a provider that serializes part
2734
+ of a body and cuts it redacts the serialized text first (`redactBeforeCut`), as
2735
+ Breeze's in-band failure text and Slides' raw chunks do. A private value of four
2736
+ to seven units that a vendor echoes only as an object key is therefore matched
2737
+ once, at the connector boundary; the agent boundary's envelope rules do not
2738
+ match values that short.
2739
+
2740
+ Redacted values can still be decision inputs. `textBodies` and `binaryBodies`
2741
+ see a Content-Type that redaction altered as `application/json`, so no altered
2742
+ type routes a body around ResultSafety as one string or as bytes; a body that
2743
+ is not JSON is then unreadable. Value safety's own decisions on values fail
2744
+ closed the same way (see "Value safety for REST vendors").
2745
+
2746
+ For an unreadable successful body, the shared mapper appends an unknown-outcome
2747
+ and check-before-repeating sentence for the call's own write request, including
2748
+ writes transported with GET. An auxiliary read lookup inside a write call
2749
+ keeps its read message; a failed lookup does not establish that the write left.
2750
+ `VendorErrors.unreadableWrite` can override the complete write message.
2751
+ Reads keep their existing wording. Overflow's
2752
+ unreviewed text responses use its write override; its reviewed health text and
2753
+ refund acknowledgement remain accepted.
2754
+
2755
+ A vendor may declare `encodedFields`: exact dotted JSON paths with whole
2756
+ array segments (`payload.parts[*].body.data`), `encoding: "base64url"`, an
2757
+ optional `sizePath` addressing the same array elements, and a one-line review.
2758
+ Construction freezes the declaration. Before ordinary whole-answer redaction,
2759
+ the kit decodes each valid field and redacts its decoded text and bytes through
2760
+ the same request-secret service. Unchanged fields retain their original
2761
+ spelling; changed fields retain the original alphabet and padding style when
2762
+ re-encoded. Both standard and URL-safe alphabets are accepted with valid padding. Invalid base64url stays on
2763
+ the ordinary redaction path. A declared size is checked against the original
2764
+ decoded byte length first; a mismatch also retains ordinary redaction. Size
2765
+ values stay unchanged. This never exempts the encoded field or its surrounding
2766
+ JSON from the generic redaction pass.
2767
+
2768
+ Result hooks alone may call `encodedFieldProvenance(answer, concretePath)`.
2769
+ Its private WeakMap is keyed by the parsed, redacted answer; each frozen
2770
+ entry holds only `originalLength`, optional `sizeValid`, and `changed`.
2771
+ Provenance contains no content and is never serialized into facts, messages
2772
+ or logs. Gmail copies this metadata privately for its decoded body containers:
2773
+ text renders after redaction and before cuts, while changed binary content is
2774
+ withheld in full. Its original byte counts still govern attachment validation.
2775
+ Vendors without this declaration retain the existing parsing path.
2776
+ Gmail refuses to update a draft whose Content-Disposition or any header name
2777
+ was redacted, because it cannot prove that rebuilding the message would retain
2778
+ its headers and attachments.
2779
+
2780
+ Credential descriptions may derive from validated connection options, and a
2781
+ probe may reject a successful answer that identifies the wrong account.
2782
+ Transport diagnostics use the vendor error label rather than the provider
2783
+ definition's title. A vendor may declare reviewed `textBodies` by status and
2784
+ operation: `text` retains decoded UTF-8 and `json-or-text` parses JSON with a
2785
+ text fallback. Other responses still require JSON. The bounded byte reader,
2786
+ sent-secret redaction, and withheld-error policy apply before text can reach
2787
+ vendor wording or a result hook; a legacy hatch's text contract does not admit
2788
+ malformed JSON on named tools.
2789
+
2790
+ The helper's connection-specific `transport(options)` supplies constant
2791
+ headers and final-URL admission outside any caller context. Admission receives
2792
+ the request method and encoded text body before authentication, so product
2793
+ exclusions can inspect JSON:API writes. `sendPath` accepts per-request headers;
2794
+ typed operations can declare header Schemas. Authentication still owns its
2795
+ headers and applies last. A provider's default call-admission policy yields to
2796
+ an explicit deployment policy. A typed refusal thrown by the probe's reply
2797
+ describer becomes the ordinary failed credential-test result.
2798
+
2799
+ `emptyRequired(schema)` preserves a legacy output object's explicit
2800
+ `required: []` through Effect's canonical JSON Schema rendering. It refuses
2801
+ an object with required properties; its decoder is unchanged. Closed empty
2802
+ input records render `properties: {}` and `required: []` explicitly.
2803
+
2804
+ A vendor may set `manifestBase` to prefix its documented operation paths.
2805
+ The prefix accepts literal path segments and whole `{param}` segments whose
2806
+ names contain only letters, digits and underscores. It is concatenated verbatim
2807
+ for the manifest; transport paths remain relative to the confined base URL,
2808
+ with no parameter interpolation. The catalog renderer
2809
+ collapses unions of bare object and array branches into a type array; branches
2810
+ with additional constraints retain `anyOf`.
2811
+
2812
+ Every answer passes through bounded reading, sent-secret redaction, vendor
2813
+ result handling, reviewed value safety and the deployment privacy projection
2814
+ before paging or provider projections can read it. Read methods beyond GET
2815
+ and HEAD require a review. Construction fixes each write step's capacity;
2816
+ the guarded transport reserves it before preparation and the shared core
2817
+ decides the whole call's write outcome. Dedicated upload builders wait for
2818
+ the Google Drive migration in C.7.
2819
+
2820
+ Reviewed path calls resolve their actual method and path through an indexed
2821
+ safety adapter; a provider-supplied template cannot bypass the policy.
2822
+ Unindexed paths require an explicit global review and no reviewed table;
2823
+ a global review never substitutes for a reviewed table's resolver. A uniform
2824
+ policy over legacy unindexed paths declares its own resolver. Sent-secret redaction
2825
+ covers response headers before result hooks can extract fragments. Withheld
2826
+ hooks admit only operation-bound status failures or unreadable bodies; other
2827
+ failures become defects whose text the API boundary withholds. Hook defects
2828
+ are rewrapped once at the tool or credential-probe edge when text is withheld,
2829
+ including defects from reviewed redaction, vendor error mapping, and the
2830
+ provider program that carry a `ConnectorCallError`. A failing visibility hook
2831
+ also withholds the defect's text; non-withheld operations retain their original
2832
+ defects.
2833
+
2834
+ ### Production HTTP dispatch (contributor reference)
2835
+
2836
+ `src/server.ts` retains the origin refusal, HTTPS upgrade and security wrapper,
2837
+ then calls the private `src/routes/dispatch.ts` adapter. Each fetch handler owns
2838
+ two immutable public Effect HttpRouters: a human table selects CIMD, connect,
2839
+ OAuth callback, metadata, health and MCP programs; the agent table selects only
2840
+ the existing agent endpoint program. The raw agent-namespace guard runs before
2841
+ human dispatch. An unmatched human route runs optional UI fallthrough, wildcard
2842
+ OPTIONS metadata/CORS, or the deployment's final 404. Core selection uses the
2843
+ router directly, without scanning the other core modules. CIMD runs before UI;
2844
+ every other human program runs after optional UI, including its null fallthrough.
2845
+ Metadata retains MCP-before-provider discovery order.
2846
+
2847
+ The synchronous constructor does no routing work or I/O. The first existing
2848
+ edge fiber builds both fixed tables with public `make`, `addAll` and
2849
+ `asHttpEffect`. Only synchronous registration and cache publication use
2850
+ `Scheduler.PreventSchedulerYield` and an uninterruptible region. The cache
2851
+ check is inside that region, so even concurrent first requests with a 16-operation
2852
+ yield budget publish only two routers. Normal scheduling and interruption are
2853
+ restored before any application callback or I/O. Cached route effects capture
2854
+ only deployment programs; they read the private current `RouteContext` service
2855
+ when executed. No request, body, signal, Promise, client, Scope or request
2856
+ services are retained in the tables. The canonical request/call ownership in
2857
+ the MCP program remains unchanged.
2858
+
2859
+ Routing uses a body-free GET projection of the URL, with percent escapes and
2860
+ the matcher's semicolon delimiter protected. The original request remains in
2861
+ `RouteContext`: existing programs own exact raw paths, arbitrary extension
2862
+ methods, JSON 405/Allow, HEAD, private OPTIONS, cookies, authentication and body
2863
+ refusal order. Router normalization cannot grant a canonical path to an encoded
2864
+ prefix, and invalid escapes in an owned suffix still reach the owning program.
2865
+ The projection introduces no second request handler or runner. Web responses
2866
+ pass through `HttpServerResponse.raw`/`toWeb` with their identity and stream
2867
+ ownership intact. The short dispatch Scope does not own MCP's body-held permit
2868
+ or the OAuth callback's uninterruptible commitment. Only a genuine public
2869
+ `RouteNotFound` before handler entry falls through; application failures,
2870
+ including structural lookalikes and genuine router errors, retain their identity.
2871
+
2872
+ Four public `effect/http/*` modules are allowed only in the private dispatcher;
2873
+ all other root-purity and optional-implementation boundaries remain enforced.
2874
+ Direct public module imports avoid the area's barrel cost. The local D3
2875
+ measurement over the reviewed D1/D2 stack is root 373,435 to 393,470 bytes gzip
2876
+ and Worker 758,274 to 774,384 (official Node 22.23.4/zlib 1.3.1-e00f703, identical
2877
+ repository bundle options). The barrel alternative measures 423,348/787,614.
2878
+ The parent accepted this measured native-routing cost with a Worker cap of
2879
+ 785,000 bytes, including room for separately measured native-auth and reviewed
2880
+ host/transport additions. Root and all other caps and historical baselines are
2881
+ unchanged by D3. Separate-input costs are not a combined gzip measurement;
2882
+ the integrated deployment must be measured again.
2883
+
2884
+ `test/server.test.ts` asserts production routing directly on Node and workerd:
2885
+ raw prefixes and malformed suffixes, extension methods, CIMD before auth/UI/body
2886
+ reads, Response/error identity, unmatched fallthrough, concurrent request
2887
+ isolation, and body-held permits through end/failure/cancel/abort/abandonment.
2888
+ `test/node.node.test.ts` bundles one unmodified production Worker and
2889
+ verifies that a queued request owns its native I/O after the preceding response
2890
+ ends. Server-route, request-admission and request-pipeline suites retain ingress
2891
+ ordering, security headers, overload/shutdown and cancellation regressions.
2892
+ Root purity, declaration and bundle checks own import boundaries and size caps.
2893
+ The frozen legacy dispatcher, source transforms, diagnostic latency loops and
2894
+ rejected Effect MCP adapters were migration evidence; their historical source
2895
+ is linked from decision 0016 rather than rebuilt by ordinary tests.
2896
+ The upward MCP protocol leaves remain SDK implementations, with D7's separate
2897
+ native-orchestration work and compatibility evidence unchanged.
2898
+
2899
+ ### Why not Schema or HttpApi for core routes
1485
2900
 
1486
2901
  Both were measured and both lost. `effect` is imported through its barrel,
1487
2902
  which defeats tree-shaking: the first conversion step measured about +20 ms
@@ -1495,17 +2910,22 @@ the operator and activity routes cost about +100 KB gzip on `./ui` and +130 KB
1495
2910
  on `./activity`, and matching the wire format meant opting out of most of what
1496
2911
  it does: unowned paths fall through rather than 404, a wrong method is a JSON
1497
2912
  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.
1504
-
1505
- The stable `4.0.0` release was re-evaluated on 2026-10-01 with
1506
- [`scripts/probes/effect-v4-evaluation.mjs`](https://github.com/zackbart/connecta/blob/main/scripts/probes/effect-v4-evaluation.mjs).
1507
- Run `pnpm run build`, then `node scripts/probes/effect-v4-evaluation.mjs output.json`.
1508
- The [raw observations](https://github.com/zackbart/connecta/blob/main/scripts/probes/effect-v4-evaluation-2026-10-01.json)
2913
+ entry imports `effect` and the four public HTTP modules confined to the private
2914
+ dispatcher (`test/purity.node.test.ts`).
2915
+ Effect 4.x is a long-term-support line, but its area barrels `effect/http`,
2916
+ `effect/http-api`, `effect/schema`, and `effect/sql` are still tagged
2917
+ `@stability unstable` and can change within it; only core modules such as
2918
+ `effect/Schema` are stable. That is why Effect is pinned to one exact version
2919
+ and every Effect upgrade is its own deliberate pull request. A deployment
2920
+ that also depends on Effect, such as the Worker example's Alchemy stack, must pin the
2921
+ same version to resolve a single copy. Connecta's own lockfile and packed
2922
+ install are checked for one copy (`test/package-surface.node.test.ts` and
2923
+ `scripts/check-package.mjs`).
2924
+
2925
+ The stable `4.0.0` release was re-evaluated on 2026-10-01 with this
2926
+ [historical probe source](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/effect-v4-evaluation.mjs),
2927
+ retained in Git history rather than the current checkout.
2928
+ The [raw observations](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/effect-v4-evaluation-2026-10-01.json)
1509
2929
  are synthetic Node requests and neutral esbuild bundles, not live-client or
1510
2930
  workerd lifetime verification. Bundle figures add representative prototypes
1511
2931
  alongside the current code; they do not claim savings from removing old code.
@@ -1514,7 +2934,7 @@ Two native input schemas with validation and JSON Schema rendering add 62,148
1514
2934
  bytes gzip to the root, taking it from 303,083 to 365,231 against a 339,526
1515
2935
  cap. Effect can reject excess properties when both decoding and rendering use
1516
2936
  `onExcessProperty: "error"`; its default strips them. The generated schemas
1517
- still differ from the exact meta-tool goldens, including record rendering and
2937
+ still differ from the published meta-tool schemas, including record rendering and
1518
2938
  composed numeric bounds. A Schema replacement would need to preserve that
1519
2939
  contract and memoized rendering. Both MCP SDK packages still depend on Zod,
1520
2940
  so changing our inputs alone does not remove Zod from an installation.
@@ -1526,7 +2946,9 @@ wrong method and an unowned path; our routes need a JSON 405 for the former
1526
2946
  and module fallthrough for the latter. Their `toWebHandler` also builds its
1527
2947
  layer immediately and owns a runner, so adopting it would require proving
1528
2948
  global-scope safety and preserving our scheduler and response-lifetime rules.
1529
- The existing Effect route programs keep those rules without this routing layer.
2949
+ Those historical defaults are not the production adapter: the narrow public
2950
+ HttpRouter dispatch above preserves explicit method refusals, unowned fallthrough
2951
+ and the existing edge runner without `toWebHandler`.
1530
2952
 
1531
2953
  Effect's MCP server has a separate compatibility gate. Its `2026-07-28`
1532
2954
  adapter accepted a stateless `tools/list`, but its `2025-06-18` adapter refused
@@ -1535,15 +2957,20 @@ next request returned 404 on a fresh handler; Connecta served the same fresh
1535
2957
  legacy list with 200 JSON and no session. The stable package exports no MCP
1536
2958
  client, so it also cannot replace our downstream SDK and OAuth implementation.
1537
2959
  [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.
2960
+ parity requirements. These historical measurements supported retaining MCP protocol and validation
2961
+ implementations. Production HTTP dispatch now uses the separately proven narrow
2962
+ public adapter above; stable v4 alone did not establish its compatibility.
1541
2963
 
1542
2964
  The core's cost is recorded rather than guessed. Against 0.24.4 the root entry
1543
2965
  grew from 235,346 to 279,526 bytes gzip, the Worker example from 263,949 to
1544
2966
  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
2967
+ `remoteMcp()`. `pnpm run check:bundle` caps the deployed shapes (the root,
2968
+ `./worker`, `./ui`, the operator UI assets, `./d1`, `./auth/access-tokens`,
2969
+ the Worker example, and the other non-provider entries) against
2970
+ `scripts/bundle-budget.json`. Per-provider entries are reported with their
2971
+ size and change from baseline but not capped: every deployment already imports
2972
+ the root, which carries the Effect core, so a provider's standalone bundle
2973
+ counts that core a second time. An install carries about 53 MB of `effect`, with
1547
2974
  no dependencies of its own.
1548
2975
 
1549
2976
  ## Where else to look
@@ -1555,7 +2982,6 @@ src/
1555
2982
  server.ts route ordering, HTTPS upgrade, security wrapper
1556
2983
  routes/ one file per surface; shared.ts holds the auth gate
1557
2984
  skills.ts MCP instructions, the usage skill, connector guides
1558
- catalog-drift.ts reviewed classifications and the drift counts a refresh produces
1559
2985
  provider.ts defineProvider(): the one maintained-provider shape
1560
2986
  activity.ts optional history factory and best-effort recorder
1561
2987
  auth/ Cloudflare Access, clerk (optional peer), downstream OAuth (remote and static)
@@ -1579,9 +3005,423 @@ compiling and configuring the real thing.
1579
3005
  - **Route order is behavior.** Moving a mutation route below the wildcard
1580
3006
  `OPTIONS` opts it into CORS preflight; reordering admission after auth makes
1581
3007
  the cheapest possible attack the most expensive request.
3008
+ - **Operation and call state live in services.** `src/runtime/call.ts` owns
3009
+ the admitted `Caller`, credentials, admission budget, recovery writer,
3010
+ classification, signals, secrets and request ledgers. Registry operations
3011
+ provision caller and operation policy; invocation adds a call lease and
3012
+ finalizes it on every exit. `api()` has a private Effect entry and a shared
3013
+ core with its Promise `callTool`. The public `ConnectorContext` is a view
3014
+ of those services. `deriveContext` shares its entry in the single view
3015
+ bridge; a spread has no caller or admission authority. Candidate credential
3016
+ probes get anonymous read-call services instead of deriving authority.
3017
+ Credentials and charges check closure before and after every await, pending
3018
+ work is interrupted, and late recovery facts are dropped. Every published
3019
+ Promise view and connector operation also races its complete caller-facing
3020
+ Promise against its owner through `settleOnClose`: credential and fetch
3021
+ views, guarded helpers, token minting, catalog loading, MCP operations and
3022
+ applicable authorization operations. An opaque storage, crypto, resolver
3023
+ or body-cleanup wait cannot keep that caller pending after closure.
3024
+ Work starts synchronously after the owner guard so shared-session waiter
3025
+ accounting and claim ownership keep their existing ordering. Late
3026
+ rejections are observed; dispatch and lease guards still contain detached
3027
+ work. Per-await I/O cancellation remains useful for releasing resources,
3028
+ while prompt refusal is enforced at the boundary
3029
+ (`test/promise-boundary-lifetime.test.ts`). Invocation owns
3030
+ the recovery and logical-call records it reads after teardown. Closing an
3031
+ outer call also revokes nested calls paused in preparation. Request-owned
3032
+ MCP sessions and token flights outlive individual calls and close with the
3033
+ request; their credential access comes from the operation. Operation views
3034
+ resolve request cancellation even without an external signal; auxiliary
3035
+ authentication, sends and response handling all use that signal. The
3036
+ invocation deadline and original request cancellation remain separate,
3037
+ so deadline cleanup cannot end request-owned auxiliary work.
3038
+ Concurrent operator probes use separate row scopes, so finalizing a failed
3039
+ row cannot revoke a sibling that is still running.
3040
+ Shared MCP sessions bind renewal preparation to the challenged call's signal,
3041
+ rather than the connection's longer lifetime. OAuth renewal
3042
+ is call-owned until its first durable claim CAS is submitted. Submission
3043
+ transfers preparation, token exchange and fenced rotation commit to the
3044
+ storage coordinator under its own deadlines and deferred-work hook; only
3045
+ the caller's wait still closes with the call. That completion
3046
+ cannot resume a closed call or resend its resource request
3047
+ (`test/call-renewal-lifetime.test.ts`,
3048
+ `test/remote-mcp-renewal-lifetime.test.ts`). Explicit request revocation also
3049
+ aborts pending authorization-code exchange and refuses late token writes
3050
+ (`test/operation-cancellation.test.ts`). Request closure
3051
+ revokes views and calls first, then runs every finalizer in reverse order,
3052
+ collecting failures. The caller's original cancellation signal is retained
3053
+ separately from the lease signal, so normal teardown never looks like caller
3054
+ cancellation (`test/call-services.test.ts`, `test/derived-context.test.ts`).
3055
+ Construction identities and immutable request/failure facts stay in the pure
3056
+ `src/provider-call-facts.ts`, keeping provider stamping and operator sinks
3057
+ free of the Effect call-service graph.
3058
+ - **A write leaves once (INV-9).** `src/write-outcome.ts` owns the shared rules
3059
+ every transport applies to a write: `unknownOutcome` turns a retryable
3060
+ failure that may follow the downstream acting (a 5xx, a gateway's 502 to
3061
+ 504, a lost connection or reply) into a non-retryable `connector_call_failed`,
3062
+ and `unsentAfterUnauthorized` reports a write answered 401 after credential
3063
+ renewal ran, which is never resent: until renewal succeeds it keeps an
3064
+ authorization code (renewal's own verdict, else the 401's), and a renewed
3065
+ credential leaves a non-retryable `connector_call_failed`. Both mark the error
3066
+ `writeOutcome: "unknown"`, which `classifyWriteOutcome` honors before any
3067
+ refusal provenance, so an authorization verdict reached after the write left still
3068
+ counts as unknown in program write accounting (`src/program-writes.ts`),
3069
+ whose result is then that verdict with its handoff, caught or not.
3070
+ `remoteMcp()` classifies by the invocation's verdict
3071
+ (`src/call-classification.ts`), falling back to `classifyTool`, and holds a
3072
+ write to one send per logical call, never per JSON-RPC message: `callTool`
3073
+ puts a dispatch record on the `tools/call` params under a symbol key (spread
3074
+ copies it; `JSON.stringify` drops it), the transport's `send` refuses a
3075
+ second message for that call whatever its id, and the tracked fetch refuses
3076
+ a second request within one message (the SDK's resend after a 401 or a
3077
+ step-up, a redirect hop) and fails a write answered with a redirect. Both
3078
+ layers refuse a missing record unless the active calls for that tool are
3079
+ all reads; catalog hints cannot overrule a served write verdict. Tracking
3080
+ loss gives a non-retryable unknown outcome, never a fresh write attempt. A write
3081
+ always gives the SDK a tool definition, so `callTool` never relists and
3082
+ resends it after a `HeaderMismatch` answer. `api()` OAuth
3083
+ (`src/auth/static-oauth.ts`), the Google Workspace client
3084
+ apply the same one-send rule to credential recovery. All maintained API
3085
+ provider failure mappers run inside `guardedFetch`. Each `api()` entry owns
3086
+ a fresh request ledger (`src/provider-call.ts`) on its derived handler context;
3087
+ further `deriveContext` copies share that ledger without sharing it with
3088
+ concurrent calls. Every guarded request records dispatch, response status,
3089
+ and provider-declared auxiliary status. Plain handler fetches and OAuth
3090
+ resource sends record their dispatch too; OAuth credential checks precede
3091
+ the resource dispatch record. Token exchanges, GitHub installation
3092
+ and repository binding lookups, token minting, and Cloudflare zone-ownership
3093
+ pin lookups are auxiliary: they cannot perform the logical resource write.
3094
+ No transport rejection proves a dispatched write unsent, whatever errno it
3095
+ names. One `fetch` call can hide several attempts: Node's fetch sends a
3096
+ request again after a 421 (even under `redirect: "manual"`) and after an
3097
+ HTTP/2 `REFUSED_STREAM`, both of which servers define as not processed, and
3098
+ an operator-installed dispatcher such as undici's `RetryAgent` can retry
3099
+ further. A refusal on the last attempt says nothing about the earlier ones.
3100
+ Remote MCP writes on Node use the owned runtime sender installed by
3101
+ `listen` from `/node`, selected before recording dispatch. Without it they
3102
+ are refused before sending. Remote MCP reads retain native fetch; Workers uses native
3103
+ fetch with manual redirects. The owned Node sender bypasses the global
3104
+ dispatcher and fetch's 421 replay, preserving the SDK's JSON/SSE bodies and
3105
+ abort signals. A reset or unconfirmed server failure remains unknown.
3106
+ Connecta never reports a transport-failed write as known failed. A write
3107
+ is a known failure only on refusal provenance recorded where the evidence is
3108
+ observed, never inferred from an error code: (a) the ledger proves it never
3109
+ left, or (b) the downstream served an HTTP refusal, a 4xx other than 408
3110
+ (`refusedStatus` and `vendorRefusal` in `src/provider-call.ts`), under the
3111
+ provider's existing served classification (a write's HTTP 409 is a `conflict`). One helper, `servedRefusal` in `src/write-outcome.ts`, decides
3112
+ (b) for the ledger and for a transport outside one: a refusing status the
3113
+ provider reads as `unavailable` or a timeout (Stripe's 424) is not proof, so
3114
+ only a genuine idempotency key keeps such a direct write's retry. Nor is a
3115
+ 401, since a server may act on a request before it rejects the credential,
3116
+ unless the transport declares reviewed `authBeforeExecution` evidence
3117
+ (`GuardedFetchOptions`: the vendor's cited guarantee and a narrow predicate
3118
+ over the raw status and headers and the request's method and path, checked
3119
+ at construction) and the predicate matched, which the ledger records on the
3120
+ request. Only Stripe declares it (`src/providers/stripe/api.ts`, reviewed
3121
+ as a `manual` check in its `drift.json`): an API v1 answer carrying Stripe's
3122
+ `Request-Id`. `test/provider-failure-sweep.test.ts` holds that list to the
3123
+ sources. Q8 is decided in one place, `settleWrite` in
3124
+ `src/write-outcome.ts`, once per logical call and over every request it
3125
+ recorded. The invocation calls it for every failure of a served write: a
3126
+ thrown or mapped error, a custom connector's, a program's call, and its
3127
+ own deadline or the caller's cancellation, when no connector failure
3128
+ arrives at all. It reads the logical call's requests
3129
+ (`logicalCallRequests`), which hold every resource request dispatched
3130
+ under the call from any ledger: nested `api()` calls, remote MCP, guarded
3131
+ transports, `ctx.fetch` and the kit. Without an invocation (a direct call,
3132
+ or a context no invocation opened), only the outermost boundary calls it,
3133
+ at the end of `providerCallFailure`, over its own ledger and every ledger
3134
+ of an `api()` call entered beneath its handler (`nestedProviderCall`,
3135
+ `nestedProviderRequests`), or over the one request a transport outside a
3136
+ ledger sent. Boundaries only record what they observed: the dispatch and
3137
+ status, a reviewed `authBeforeExecution` match, the 401's own mapped verdict
3138
+ (`vendorRefusal`), and a renewal of that one request's own credential
3139
+ (`renewedAfterUnauthorized(request, sent, fresh)`, only for a credential
3140
+ other than the one it sent). The rule: a write request answered 401 that
3141
+ is not a declared refusal standing for the whole call makes the call
3142
+ unknown, never retryable, with no wait and no resend or key-reuse advice;
3143
+ while any such 401 is unrenewed the verdict keeps an authorization code,
3144
+ so invocation keeps the `authorize_connector` handoff and picks its
3145
+ reconcile-first words. Its code and words are the failure's when that is an
3146
+ authorization verdict, else the 401's own recorded verdict, else
3147
+ `auth_required`, with a sentence saying the write was sent before the
3148
+ vendor rejected its credential unless the verdict already accounts for the
3149
+ 401 (a renewal's words). When every such 401 was renewed, the handoff goes
3150
+ and the failure keeps its words, unknown and without a wait. A declared 401
3151
+ stands only as the call's one request, sent by the boundary that decides,
3152
+ whose own refusal is the failure (and, served, known for the logical call).
3153
+ The recorders are the guarded transport (dispatch, status, declaration
3154
+ match, mapped verdict), `ctx.fetch` (dispatch, status), static OAuth
3155
+ (status, verdict, renewal), Google (per-attempt entry, renewal of a read's
3156
+ replay), the kit (core ledger entry, status, `recordUnauthorized`,
3157
+ refresh-once renewal),
3158
+ `remoteMcp()` (each `tools/call` as an entry under its caller's ledger and
3159
+ logical call, status, the verdict, and renewal by comparing the
3160
+ `Authorization` of the resend the SDK attempts), and `api()` itself (one
3161
+ ledger per call, linked to the one whose handler entered it). The same
3162
+ decision settles a call that returned (`settleWriteResult`, before its
3163
+ result is processed in the invocation, and in `providerCallResult` at an
3164
+ outermost direct boundary, including a guarded transport outside any ledger):
3165
+ any undeclared write 401 blocks success, even after renewal. Renewal drops
3166
+ only the handoff; a program still counts the write in `writes.unknown`.
3167
+ A caught read 401 needs positive operation or tool classification, plus a
3168
+ recorded GET or HEAD, to leave success intact; a method alone proves no
3169
+ read. The guarded transport records its operation classification, otherwise
3170
+ its own `api()` tool supplies it (including nested read tools).
3171
+ `ctx.fetch`, OAuth and the kit record the same evidence. The final outgoing
3172
+ `X-HTTP-Method-Override`, `X-HTTP-Method` or `X-Method-Override` header
3173
+ makes the request a write regardless of its classification or method. A
3174
+ declared refusal followed by a successful write also leaves success intact.
3175
+ A custom guarded transport delegating through `ctx.fetch` or `ctx.oauth.fetch` records the wrapper
3176
+ separately from each physical send. Each physical entry records its own
3177
+ outgoing method and response; a later response cannot erase an earlier
3178
+ 401, and a delegated POST cannot inherit a wrapper's GET read evidence.
3179
+ The inner entry does not inherit the wrapper's declaration or explicit
3180
+ read evidence, including when the init is copied. Until linked recording
3181
+ is reviewed, that duplicate may conservatively keep a successful
3182
+ composite unknown even when the wrapper proves its own refusal or read.
3183
+ Request entries are append-only: every physical send, including a read
3184
+ replay after OAuth renewal, gets its own entry. Dispatch refuses an entry
3185
+ already sent and locks its sent method and dispatched flag; the request's
3186
+ own response status is recorded once and locked, and its own renewal can
3187
+ only add a locked mark. These fields are readonly in TypeScript too.
3188
+ The built-in `oauthBearer` transport hands an unsent entry to a known
3189
+ recording OAuth grant once, before any dispatch. The grant consumes that
3190
+ handoff before credential reads and creates a new entry for each further
3191
+ send. It never borrows a dispatched entry. Returned-response identity
3192
+ names the entry whose status was already recorded, without recording it
3193
+ again. This preserves the built-in transport's refusal/read provenance;
3194
+ custom delegation and copied init objects remain conservative.
3195
+ Remote MCP captures
3196
+ each physical read's entry before awaiting its response; a later send
3197
+ cannot overwrite it. The kit's response identity and Google's init identity
3198
+ identify the request whose credential was renewed. The legacy guarded-fetch
3199
+ `answeredRequest` helper has no production caller; tests retain it to exercise
3200
+ renewal provenance outside the kit.
3201
+ The decider normalizes every failure it settles,
3202
+ typed or not (a raw network error, an `AbortError`, a thrown string). Its
3203
+ verdict is final (`settledWrite`, which follows copies and redaction): the
3204
+ invocation reads the mark before anything rebuilds the details, and its
3205
+ enrichers then add no key-reuse, resend or wait advice; only the
3206
+ reconcile-first `retry` words and the handoff. A 408 is a
3207
+ timeout, and a 2xx followed by a mapper, parser, or output-validation
3208
+ failure may have applied. `classifyWriteOutcome` counts a
3209
+ dispatched write `failed` only with that provenance (`answered`, or the
3210
+ error's private known-failure mark); a bare `rate_limited`,
3211
+ `invalid_args`, or `conflict` code is `unknown`. A downstream tool's
3212
+ `isError` is a read's answer only: for a write it arrives over a 2xx after
3213
+ the handler ran, so it is unknown and non-retryable, and its text stays.
3214
+ Refusal evidence holds only for its whole logical call. The invocation
3215
+ opens one per dispatch (`openLogicalCall` in `src/provider-call.ts`), and
3216
+ every `api()` and `remoteMcp()` `callTool` entered beneath it joins it
3217
+ (`enterProviderBoundary`). A served write's mark stands
3218
+ (`knownForLogicalCall`) only when at most one boundary was entered and the
3219
+ invocation invoked that boundary object with its own `callTool`, or a
3220
+ maintained connector object declared to delegate each call to one boundary
3221
+ (`delegatesEachCall`, GitHub) with its declared function. Identities are
3222
+ captured, never read back: the invocation reads the connector and its
3223
+ `callTool` once and invokes that function with `Reflect.apply`, never
3224
+ through the function's own `call` or `apply` property; each boundary
3225
+ records its object with the published function when it is built
3226
+ (`providerBoundary`) and names that object, from its closure, on entry;
3227
+ and a delegator is declared at construction. A connector that reassigns
3228
+ `callTool` mid-call, or a copy (`{ ...boundary }`, `Object.assign`) that
3229
+ shares a boundary's or delegator's function, borrows no standing;
3230
+ `defineProvider`'s inheriting stamp keeps its connector's
3231
+ (`aliasCallIdentity`). A boundary marks a write known only when
3232
+ every request it dispatched is proven unapplied, so the rule reads: known
3233
+ only if every request the logical call dispatched is proven unapplied and
3234
+ the failing request carries the refusal. A custom connector that composes
3235
+ calls, or that could send around them, never inherits an inner verdict; the
3236
+ invocation withdraws the mark and the write is unknown.
3237
+ Write inference also needs positive evidence before relaxing timeout or auth
3238
+ reconciliation. `LogicalCall.auxiliaryReads` records each auxiliary GET or HEAD
3239
+ at dispatch, without method-override headers, separately from the call-owned
3240
+ requests in `sent`. Its physical read proof does not depend on the served
3241
+ tool's write classification. `onlyAuxiliaryRequests` requires exactly one
3242
+ boundary that owns the logical call and declares complete request tracking
3243
+ (`tracksEveryRequest`), at least one dispatched auxiliary resource read, and
3244
+ no dispatched call-owned request or kit auxiliary falsely declared as a read.
3245
+ The kit records `blocksAuxiliaryProof` at dispatch for an `effect: "read"`
3246
+ request without physical GET/HEAD proof; `nonReadAuxiliaryDispatched` retains
3247
+ that blocking fact on the logical call. The
3248
+ invocation's `mayHaveWritten` is true
3249
+ for an entered write unless the call answered with that proof; an explicit
3250
+ `writeOutcome: "unknown"` always overrides the relaxation. Empty or partial
3251
+ ledgers, token POSTs alone, multiple boundaries, custom `api()` and
3252
+ `remoteMcp()` connectors, deadlines, and caller cancellation supply no such
3253
+ proof. Only maintained providers have the internal tracking declaration;
3254
+ they must route every dispatch through their tracked transports. The kit
3255
+ still classifies auxiliary POSTs and method-overridden GETs as writes for
3256
+ reservation and replay; the effect label does not change those rules.
3257
+ Their dispatches block auxiliary-only inference even beside a genuine
3258
+ auxiliary read, and count as call-owned requests for write-failure settlement,
3259
+ including a 503 or a mapped `unavailable` verdict. Guarded authentication
3260
+ token POSTs supply no read proof,
3261
+ but do not invalidate a later auxiliary resource read. A conservative
3262
+ timeout withdraws `answered`; a conservative auth failure does so only with
3263
+ an empty resource ledger, also withdrawing that ledger's private known-failure
3264
+ mark before advice is composed. A refused call-owned write keeps its
3265
+ `answered` evidence and resource-refusal provenance only with a complete
3266
+ ledger proving the refusal covers the whole call; its auth recovery advice
3267
+ uses the same known outcome as program accounting. Partial or unknown ledgers
3268
+ remain conservative, as do token-mint and auxiliary auth failures without
3269
+ positive resource-read proof. Auth failures with no
3270
+ call-owned dispatch, including credential refusals and static-OAuth refresh
3271
+ failures, keep conservative program accounting and reconciliation wording.
3272
+ Pre-dispatch refusals mark themselves: `api()`'s unknown tool and argument
3273
+ validation, `remoteMcp()`'s allowlist and non-auth failures before its
3274
+ `tools/call` is sent, and GitHub's REST target check and token mint. Auth
3275
+ failures after entering a write with an empty resource ledger remain unknown,
3276
+ including a `remoteMcp()` connect or initialize 401 before `tools/call`.
3277
+ `remoteMcp()` applies the rule to direct and served writes alike: once
3278
+ `dispatch.sent`, a JSON-RPC error (`-32602` included), an unreadable result,
3279
+ or a failed output validation is unknown; only an HTTP 4xx (not 408) on the
3280
+ request is a refusal. A downstream's `input_required` is no proof either: a
3281
+ handler asks for input from inside its run, so a write's unsupported
3282
+ suspension (`src/invocation.ts`) and a direct write's failed relay
3283
+ (`src/downstream-input.ts`) are unknown and say to reconcile, not resend.
3284
+ The invocation finalizes every served write `classifyWriteOutcome` calls
3285
+ unknown before it adds advice: `retryable: false`, `writeOutcome:
3286
+ "unknown"`, and no grant-then-retry or correct-and-retry text, so a custom
3287
+ connector's code cannot advertise a retry. Advice to send again (a rate
3288
+ limit's "back off before retrying", a token or OAuth preflight's "then
3289
+ retry authorization") is a segment of its own, attached with
3290
+ `resendAdvice` in `src/errors.ts` where it is written and never part of
3291
+ the error's message. Output composes it only once the outcome allows it:
3292
+ `classifyCallError` for a direct caller, unless the write is already
3293
+ unknown, and the invocation after its verdict (it classifies with
3294
+ `classifyWithoutAdvice`). So no redaction of a private argument, no
3295
+ wrapper's rewording, and no preflight failure after an earlier dispatch
3296
+ can leave it on an unknown write, while a read and a known refusal keep it.
3297
+ Key-reuse advice ("reuse this key", "cannot repeat the write") needs more:
3298
+ proof that its key protects the whole logical call
3299
+ (`keyProtectsLogicalCall`). Every resource request dispatched under the
3300
+ logical call, from any ledger, is recorded on it once per dispatch with
3301
+ its method and the genuine key it carried (`ProviderRequest.key`, set by
3302
+ the kit's guarded transport only for a key fetch sends as given). The advice stands only when
3303
+ exactly one boundary was entered, the invocation's object owns it as for
3304
+ `knownForLogicalCall`, that boundary tracks every request, and exactly one
3305
+ dispatched request was a write, carrying the named key; a request counts as
3306
+ a read only with positive operation/tool classification and a recorded
3307
+ GET or HEAD (`readRequest`); method-override headers make it a write. A composite, a
3308
+ wrapper, a second write, an earlier unrelated key, or a call interrupted
3309
+ after two dispatches gets no such advice; the key stays a diagnostic, in
3310
+ the message and `uncertainCall.recovery`. The REST write tool's
3311
+ generated-key advice is a segment of its own (`keyResendAdvice`), which the
3312
+ invocation composes only under that proof and `classifyCallError` composes
3313
+ for a direct caller of the one boundary that sent the key.
3314
+ An authorization or credential handoff on an
3315
+ unknown write says to re-read the target before any fresh call instead of
3316
+ "retry the original call" (`src/authorization-handoff.ts`). The guarded
3317
+ `HttpClient` owns transport attempts through `src/guarded-transport.ts`;
3318
+ Node uses the owned sender in `src/http/node-transport.ts`, installed by
3319
+ `/node` when `listen()` starts serving. Reads keep their retry advice. Every maintained
3320
+ provider declares (`tracksEveryRequest`) that its handlers send only through
3321
+ channels the ledger records; for an undeclared `api()` handler an empty
3322
+ ledger is no evidence, so its served write fails unknown once the handler
3323
+ has run. When the handler settles, `api()` closes the ledger before any
3324
+ decision: a resource request still waiting to leave (a sibling reading its
3325
+ credential) is refused before its credential read and again before send,
3326
+ and one in flight already counts as dispatched.
3327
+ `providerCallFailure` in `src/write-outcome.ts` finalizes the call once at the
3328
+ `api()` boundary, including handler-thrown typed errors. For a served write,
3329
+ no resource dispatch keeps the failure's own advice. A vendor's own 4xx
3330
+ refusal (a 4xx other than 408) is a known failure only when no other
3331
+ resource request was dispatched. Refusal provenance travels with the error independently of its
3332
+ code or wording, so a generic `connector_call_failed` refusal still counts
3333
+ as failed in program write accounting. Every other possibly executing
3334
+ failure is non-retryable with `writeOutcome: "unknown"`; authorization codes
3335
+ keep their handoff. A write answered 401 is one of these unless its
3336
+ transport declares `authBeforeExecution` evidence that matched, and it
3337
+ keeps an authorization code and handoff whatever failure surfaced after
3338
+ it, with no wait.
3339
+ A later refusal cannot make an earlier successful request safe to repeat.
3340
+ Provider recovery wrappers change wording through helpers that preserve
3341
+ verdicts and provenance; Google also retains its response phases and reasons.
3342
+ No operation hint (a whole-resource `PUT`, fixed cells, a content address)
3343
+ grants retry credit, direct or served. A served write requires
3344
+ reconciliation even with an idempotency key; direct transport calls
3345
+ without a served verdict can still prove repeatability, but only with a
3346
+ genuine key sent to a vendor that honors it (Stripe today, through
3347
+ `hatch.idempotency`): the REST write tool refuses an `idempotencyKey` that fetch
3348
+ would send empty after trimming or would not send at all (CR, LF, NUL, any
3349
+ other control character but tab, DEL, or a code point past Latin-1), as
3350
+ `invalid_args` before recovery is recorded, and the guarded transport marks a request
3351
+ repeatable only for a key fetch sends as given.
3352
+ `providerCallFailure` (`src/write-outcome.ts`) is the one finalizer for a
3353
+ direct write's failure: the `api()` handler boundary, a guarded transport
3354
+ outside a call ledger, and every `remoteMcp().callTool()` write, direct or
3355
+ served (with its `tools/call` dispatch record as the ledger and the
3356
+ endpoint's HTTP status as the served verdict) all hand it every write
3357
+ failure. Guarded transports
3358
+ inside a call (`ctx.fetch`, the shared REST layer, Google) defer to the
3359
+ `api()` boundary. No identity passthrough or code filter runs ahead of it
3360
+ for a direct write. It recognizes every timeout and abort before any code
3361
+ filter: the caller's own signal (an `AbortSignal.timeout` included, and
3362
+ however the runtime's fetch reports it), an SDK or connecta deadline
3363
+ (`code: "timeout"`, an HTTP 408), and a timeout errno. Without a genuine
3364
+ key, such a write is unknown and non-retryable: a timeout keeps its code
3365
+ and `details.code`; the caller's abort becomes `timeout` or
3366
+ `connector_call_failed` with its reason as `cause` (a redacted copy of the
3367
+ same class where output redaction applies, as on `remoteMcp()`) and its
3368
+ class and code in `details`. No invocation exists to report it as
3369
+ `write_outcome_unknown`. The caller's own abort reason still passes by
3370
+ identity for a read and for a served call, whose timeout passes through
3371
+ unchanged for the invocation to report.
3372
+ The catalog-driven `test/provider-failure-sweep.test.ts` checks every API
3373
+ provider/tool on Node, fails on missing provider/tool examples or a
3374
+ dispatched failure bypassing the helper, and covers served write/read
3375
+ overrides with HTTP 503 for every tool and a connection reset after send
3376
+ for each provider/verdict representative. HTTP 401 recovery uses those
3377
+ representatives plus the distinct composite, auxiliary, owner-lookup and
3378
+ POST-as-read paths.
3379
+ Shared sender and real-socket evidence below own status/reset permutations.
3380
+ Local metadata
3381
+ tools prove zero sends; composite calls prove each distinct request leaves
3382
+ once. The round-5 cases additionally cover Docs creation with initial text,
3383
+ composite success followed by refusal, GitHub preflight failures, typed
3384
+ handler errors after successful sends, Forms cursor conflicts after 200,
3385
+ generic-code vendor refusals, auxiliary failures after resource dispatch,
3386
+ redaction of refusal errors, and concurrent direct-call ledger isolation.
3387
+ The round-6 cases cover a composite write's early refusal while siblings
3388
+ await credentials or are in flight, requests started after the call ended,
3389
+ connect-phase errnos with each body kind (all unknown for a write, and a
3390
+ direct read stays retryable), a request aborted with a refusal-shaped
3391
+ reason, an errno outside fetch's network error, and
3392
+ undeclared handlers whose untracked send precedes a typed failure. Each
3393
+ catalog case also checks that the ledger recorded every resource request
3394
+ the network saw, and the inventory that every API connector declares it.
3395
+ `test/write-evidence.test.ts` uses real refused, reset, and held sockets on
3396
+ both runtimes: a refused write is unknown and a refused read retryable,
3397
+ including a write aborted with another fetch's real refusal as its reason
3398
+ after the server read its body; `test/write-evidence.node.test.ts` covers a
3399
+ Planning Center POST that undici resends after a 421 to a closed listener, a
3400
+ DELETE that a `RetryAgent` resends after a reset, and a streamed body and a
3401
+ `Blob` with an overridden `stream()` that undici partly sends before its
3402
+ producer fails. Each has the server holding the write and reports unknown.
3403
+ Additional tests:
3404
+ `test/remote-mcp-write-once.test.ts` (real SDK transport),
3405
+ `test/api-oauth.test.ts`, `test/google-workspace-delegation.test.ts`, and each
3406
+ provider's `INV-9` tests.
3407
+ - **Plain `ctx.fetch` is not confined to an origin, by design.** In an `api()`
3408
+ handler it is the global `fetch` plus sent-secret registration. It carries no
3409
+ grant: only `ctx.oauth.fetch` attaches the OAuth token, and only to
3410
+ `apiOrigins`, and every maintained provider sends through a guarded transport
3411
+ confined to its base URL. A custom handler is deployment code, which owns its
3412
+ destinations (goal 4); no agent program reaches `ctx.fetch`, so INV-3 holds
3413
+ below the sandbox, and a credential a handler sends is redacted from what the
3414
+ agent sees (INV-5). Confining it would need a declared origin list on every
3415
+ `api()` connector and would break handlers that follow a URL their API
3416
+ returned, such as a signed download. `test/api-oauth.test.ts` pins that the
3417
+ plain fetch carries no grant.
1582
3418
  - **`close()` is idempotent and ordered.** Both admission pools, then the
1583
3419
  connector limiters, then the executor; Node's `listen()` calls it on
1584
- SIGTERM/SIGINT.
3420
+ SIGTERM/SIGINT. The listener stops accepting connections and closes idle
3421
+ keep-alive sockets plus accepted sockets that have not produced their first
3422
+ HTTP request, then waits for active responses, deferred work, and executor
3423
+ cleanup. `test/node.node.test.ts` covers shutdown after token adoption,
3424
+ rotation, and doctor with an unused connection and a live QuickJS execution.
1585
3425
  - **Structural mistakes throw at construction.** A duplicate connector id, an
1586
3426
  invalid admission rule or limit, an unknown option at any depth, the old
1587
3427
  boolean `accessTokens` option, malformed or repeated `agents` or agents
@@ -1601,3 +3441,317 @@ The contract now lives in [Operator UI](./operator-ui.md#operator-data-contract)
1601
3441
  including configuration provenance, live facts, activity disclosure, doctor,
1602
3442
  and the browser implementation. [Optional deployment modules](#optional-deployment-modules)
1603
3443
  keeps the module composition and request-lifetime rules here.
3444
+
3445
+ Vendor error declarations may review structural text presence at fixed body
3446
+ paths. The error boundary exposes only `present` and `nonBlank`, after
3447
+ sent-secret redaction, and never the text. Reviewed recovery-token lists can
3448
+ set a fixed `displayFacts` bound with a reason; other lists display eight
3449
+ entries. Both reviews are snapshotted at connection construction and never
3450
+ change classification or the operation's error-withholding policy.
3451
+
3452
+ Reviewed numeric error-body paths expose finite, nonnegative numbers only.
3453
+ Nonblank numeric strings may be converted; empty or blank strings are absent.
3454
+ The original text never reaches classification. These paths are also captured
3455
+ at construction and cannot change error-withholding policy.
3456
+
3457
+ Native pools resolve names through a shared claimant index for listing and
3458
+ calling, then use the existing invocation routes. Native recovery adapts only
3459
+ Connecta error envelopes in `src/native-recovery.ts`; provider dispatch and
3460
+ continuation enforcement stay in their existing owners.
3461
+
3462
+ ### Contributor reference: owned Node transport dispatch
3463
+
3464
+ `src/guarded-transport.ts` is the platform-neutral runtime slot. `sendGuarded`
3465
+ invokes a dispatch callback before legacy/fake sends; a transport declaring
3466
+ `delaysDispatch` must invoke it exactly once at its last safe pre-send boundary.
3467
+ The Node sender does this before a direct send or after CONNECT/TLS establishment,
3468
+ before exposing the socket to undici's destination Client. A refusal there must
3469
+ never reach the destination or acquire dispatch provenance. Each send owns its
3470
+ Client/ProxyAgent and response lifetime, with HTTP/1.1, non-idempotent dispatch,
3471
+ no interceptors and no redirects. No dispatcher, response, callback or signal is
3472
+ cached across requests. This seam is transport policy; SDK protocol, auth and
3473
+ request/coordinator ownership remain with their existing adapters.
3474
+
3475
+ ### Contributor reference: OAuth credential transport seam
3476
+
3477
+ `sendCredentialPost` in `src/guarded-transport.ts` is a Promise platform leaf for
3478
+ SDK-authenticated token, registration and revocation POSTs. It resolves the
3479
+ owned policy, forces manual redirects and withholds native exception text/cause,
3480
+ while preserving the caller's original abort reason. The SDK still constructs
3481
+ auth headers/forms and decodes the byte-safe response; this leaf supplies no
3482
+ OAuth retry, grant, response-decoding or ownership orchestration.
3483
+
3484
+ `OAuthRefreshCoordinator.coordinatedFetch` accepts an optional final synchronous
3485
+ `prepareCredentialRequest` hook. Production adapters resolve `guardedTransport(true)`
3486
+ there before claiming a code/refresh grant, so an unsupported Node host consumes
3487
+ neither. The base-fetch leaves in static OAuth and remote MCP call
3488
+ `sendCredentialPost`; remote registration and disconnect revocation do so too.
3489
+ D4 must preserve this preflight and the distinction between request-owned code
3490
+ exchange and coordinator-owned refresh after claim submission. The existing
3491
+ spent-token algorithm intentionally remains conservative after a submitted claim,
3492
+ even if a later CONNECT fails before destination dispatch. D6 must preserve the
3493
+ SDK JSON-RPC envelope distinction at the leaf: protocol POSTs are not credential
3494
+ POSTs; forms and registration JSON are. Discovery GETs remain native fetch.
3495
+
3496
+ ### Contributor reference: Node response drain
3497
+
3498
+ The Node listener counts active ServerResponses per socket. Once shutdown starts,
3499
+ each finish/close removes its response; the last completion calls `destroySoon`
3500
+ to flush queued bytes and close the connection. This also covers SSE whose
3501
+ headers were sent before shutdown and pipelined responses waiting behind an
3502
+ inflight response. Normal keepalive remains Node-owned until shutdown. Idle and
3503
+ never-admitted sockets retain their separate close paths, and the configured
3504
+ shutdown deadline still forces exit 1 for work that never completes.
3505
+
3506
+ ### Native auth programs (contributor)
3507
+
3508
+ Inbound verification, shared authentication admission, machine-token verification,
3509
+ static OAuth and the SDK-facing downstream OAuth provider compose private native
3510
+ programs. `auth/native.ts` selects registered implementations by the current
3511
+ function identity; an original Promise shell enters its own implementation
3512
+ without re-reading an overridden method. Class registrations retain their
3513
+ original method and prototype/key separately, so instance, inherited and
3514
+ prototype decorators can delegate to a saved original without recursion. Custom
3515
+ adapters and overridden methods retain their Promise boundary and original errors.
3516
+
3517
+ `auth/native-call.ts` provides the canonical `RequestScope` Context from the
3518
+ existing call view. Native grant preparation and storage reads execute inside
3519
+ that Context and refuse a closed owner; no second request-identity table is
3520
+ introduced. Keeping that call-only module separate also keeps downstream call
3521
+ services out of metadata and operator UI imports. Native storage uses D2's
3522
+ registered storage programs; custom stores remain one-operation Promise leaves.
3523
+
3524
+ `authFlow` composes yielded Effects while delivering original typed failures to
3525
+ local auth policy catches. Interruption remains interruption, runs yielded
3526
+ cleanup and never becomes a diagnostic Cause. A fetch's settlement observer
3527
+ cancels a late body. The resource sender records only retained status/refusal
3528
+ facts after cancellation; it cannot renew a call capability or send another
3529
+ request. Synchronous abort observers finish recording those facts before native
3530
+ wait cleanup removes them.
3531
+
3532
+ Admission charges track the actual settlement of SDK, crypto, fetch and custom
3533
+ Promise leaves. Submitted native storage work has independent settlement so a
3534
+ caller-facing deadline cannot release its charge early. Isolate-wide admission
3535
+ state remains scalar charges, counters and weak reachability; no request-owned
3536
+ client, signal, body or Promise is cached across requests. Organization cache
3537
+ TTLs, lookup ordering and rate-limit backoff remain unchanged.
3538
+
3539
+ Admission composes explicit native continuations rather than suspending a
3540
+ generator that contains its charge token. The workerd collection regression
3541
+ holds live middleware work charged, ends a different invocation, then proves
3542
+ its unreachable charges are collectable. Neither deadline nor abandonment
3543
+ releases a charge; only actual settlement or proven unreachability does.
3544
+
3545
+ Refresh claims still transfer ownership synchronously with claim-CAS submission,
3546
+ before its answer. The bounded native coordinator starts without request/call
3547
+ Context authority and may persist a rotation after its caller closes. A closed
3548
+ caller cannot consume that result or resume sending. The cross-request refresh
3549
+ join deliberately crosses a Promise boundary so workerd resumes each waiter in
3550
+ its own request. Code exchange remains request-owned and one-use. Epochs,
3551
+ permanent spent fingerprints, physical-attempt guards and pre-claim transport
3552
+ preflight are unchanged.
3553
+
3554
+ `authStorage(storage, key, args, beforeSubmit?)` admits the ownership hook and
3555
+ native/custom storage invocation in one JavaScript reaction. Its public
3556
+ Scheduler delegation suppresses automatic yield only during that initial
3557
+ reaction; a microtask restores parent scheduling before pending I/O resumes.
3558
+ The four hook sites are `updateGrant`'s guarded write and `claimRefresh`'s
3559
+ unknown-lease refusal, ambiguous spent-token write and new claim. A forced
3560
+ scheduler regression covers both native and custom stores; real pending TCP
3561
+ I/O, pre-submission cancellation, synchronous failure and unknown CAS outcomes
3562
+ retain their distinct ownership and exactly-once settlement rules.
3563
+
3564
+ Intentional leaves are the Clerk and MCP SDKs (including their callback methods),
3565
+ platform fetch/body readers and Web Crypto, custom identity/gate/sealer/storage
3566
+ hooks, and the reset-settlement observer required by the callback route. Native
3567
+ programs compose around those leaves directly. Public Promise APIs, SDK method
3568
+ signatures and published private class declarations remain unchanged; only
3569
+ `runtime/run.ts` starts fibers. SDK telemetry remains disabled, and raw SDK,
3570
+ body, storage or Cause diagnostics never reach operator records.
3571
+
3572
+ The declaration comparison retains 86 of 88 reachable files byte for byte.
3573
+ The two exact exceptions swap only adjacent `title` / `instructions` members
3574
+ in the inferred provider and Google Workspace schema declarations; both keep
3575
+ the same `Field<unknown, unknown, true>` type. Both owning source files are
3576
+ unchanged. A narrow machine check restores only those two exact adjacent
3577
+ pairs and requires byte equality, with no other type, export or reference
3578
+ change. All 88 declarations still exclude Effect and have complete references.
3579
+
3580
+ The superseding 2026-10-25 D3/D4 cost ruling authorizes only Worker
3581
+ `maxGzip: 785000` (previously 760000) and Clerk `maxGzip: 85000` (previously
3582
+ 63087). Official cached Node 22.23.4 / zlib 1.3.1-e00f703 measured Clerk at
3583
+ 47,688 -> 80,961 bytes gzip (+33,273): the generated SDK is unchanged, while
3584
+ native Effect adds 91.8 KB raw plus the native helper and small auth-source
3585
+ growth. The isolated D4 candidate measured Worker at 762,666, root at 377,177
3586
+ and D1 at 88,321 bytes gzip. Optional-graph optimization brought UI to 342,955,
3587
+ so the earlier unused UI allowance is revoked and its original 345000 cap
3588
+ remains. Historical baselines, root/D1 and all other caps, dependency pins and
3589
+ security/API gates remain unchanged. These independent measurements do not
3590
+ predict a combined gzip by addition; final artifacts must be rebuilt. Native
3591
+ registration, decorated fallback and request/native-storage semantics also
3592
+ require a smoke through an actual minified production bundle. Final post-hook
3593
+ official artifacts measure root 377,290, D1 88,321, Clerk 80,959, UI 342,959
3594
+ and Worker 762,774 bytes gzip, all within those caps.
3595
+
3596
+ ### Native remote MCP programs (contributor)
3597
+
3598
+ Remote MCP connection joins, credential and epoch checks, catalog intake,
3599
+ negotiation, calls, resources, Skills, auth and status compose native programs in
3600
+ `connectors/remote-mcp.ts`. `catalog-cache.ts` owns native SDK cache reads,
3601
+ chunked publication, generation fencing, complete refresh acceptance and listing
3602
+ serialization; `connectors/negotiation-cache.ts` owns native negotiation storage.
3603
+ The published connector, cache and SDK callback methods remain Promise shells.
3604
+ Native consumers select those programs through `authCall` / `authInvoke` by
3605
+ current function identity. Each original shell enters its own immutable program,
3606
+ so a decorator or inherited delegate can call a saved original without recursion.
3607
+ `invocation-auth.ts` follows that same identity dispatch for local credential
3608
+ checks. The remote call registers its final original function with
3609
+ `runtime/call-entry.ts`, preserving invocation validation, policy and call services.
3610
+ A replaced function cannot inherit that native call proof.
3611
+
3612
+ Connector and cache state use the canonical `RequestScope` from the existing
3613
+ view services. The shared connection provides only that request owner, never the
3614
+ first waiter's call services, credentials or budget. SDK cache callback edges
3615
+ restore that same owner. Every operation captures its own generation, credential
3616
+ identity and listing binding before waiting; a later listing cannot adopt an
3617
+ abandoned publication. Request closure revokes the connector and cache scopes.
3618
+ No client, transport, response, signal, promise or live request Context is shared
3619
+ across inbound requests. D4's bounded refresh coordinator retains its separate
3620
+ ownership, synchronous submission gate and waiting-request handoff.
3621
+
3622
+ Two deliberate Promise waits remain: a connect waiter receives the Deferred's
3623
+ answer in its own workerd request continuation, and a serialized listing waits
3624
+ for a Promise turn before running its native program. Cancelling a queued
3625
+ listing releases its turn only after its predecessor, so it cannot unlock a
3626
+ listing still using the SDK cache binding. Cancellation after entry ends that
3627
+ listing's publication without starving a live sibling. Teardown starts native
3628
+ finalizers before checking caller ownership and joins their bounded independent
3629
+ settlement. Only `runtime/run.ts` starts fibers.
3630
+
3631
+ Physical sender Promise observers retain response status, write refusal and
3632
+ redirect facts even when a native waiter closes first. The call's abort path
3633
+ yields one scheduler reaction before settling those facts, including when the
3634
+ SDK rejects synchronously from the abort event. These observations grant no live
3635
+ authority: a closed caller cannot renew, send, publish a cache result or consume
3636
+ late output. Source/effective write classification, one physical attempt,
3637
+ withheld transport text, explicit downstream tool errors, input suspension,
3638
+ final output validation and original abort/error identity remain enforced.
3639
+
3640
+ Intentional leaves are the pinned MCP SDK's actual client/transport operations
3641
+ and schema validators, the owned one-attempt sender and platform fetch,
3642
+ Web Crypto fingerprints, bounded byte/SSE stream readers and body cancellation,
3643
+ and unregistered custom token, credential, storage, sealer or observation hooks.
3644
+ SDK `ResponseCacheStore` is the callback contract, not a replacement client or
3645
+ protocol implementation. Its storage and refresh algorithms are native programs;
3646
+ its Promise shells are required SDK edges. The connector keeps modern/legacy
3647
+ negotiation, header mirroring, cache partitions and TTL semantics. No SDK/Effect
3648
+ pin or supported protocol surface changes.
3649
+
3650
+ Native storage and activity selection in `runtime/native-storage.ts` checks the
3651
+ current methods against their original registered identity, and selected programs
3652
+ check again when executed. A replacement or decorator owns the Promise fallback;
3653
+ restoring originals restores native composition. Registry storage namespaces
3654
+ select the underlying current methods at dispatch, including namespaces created
3655
+ while a source method is decorated. Original SQL shells call their own native
3656
+ program directly. Tests cover current and previously selected programs, own and
3657
+ inherited saved-original delegates, get/set/CAS and activity record/list,
3658
+ restoration, typed failures and interruption. The D4 native proof fixtures install
3659
+ their intentional throwing Promise shells before registration, so they establish
3660
+ native composition without authorizing bypass of later replacements.
3661
+
3662
+ `remote-mcp-native.test.ts` runs native SDK/cache/request-boundary cases on Node
3663
+ and workerd. `remote-mcp-native-bundle.node.test.ts` additionally loads an actual
3664
+ minified production bundle to verify call/cache registration, request Context,
3665
+ native storage, saved-original delegates, custom replacements and closed-owner
3666
+ refusal. Existing wire, physical-attempt, cache and OAuth lifetime suites remain
3667
+ the compatibility evidence.
3668
+
3669
+ ### Native upward MCP orchestration (contributor)
3670
+
3671
+ `routes/mcp.ts` composes server preparation, native catalog selection, exchange,
3672
+ output redaction and response-owned request cleanup as native programs. The
3673
+ pinned SDK's modern `createMcpHandler` and a fresh stateless legacy JSON transport
3674
+ remain the protocol implementations. Effect 4.0.5's public MCP adapters cannot
3675
+ serve the required fresh legacy contract; this implementation does not adopt
3676
+ Effect McpServer. Each inbound request constructs fresh servers and keeps its
3677
+ admission scope until its response body ends, fails, is cancelled or reaches its
3678
+ existing deadline. Authentication and endpoint admission precede body dispatch.
3679
+ The declared-agent observation stays after successful endpoint admission and
3680
+ before listen classification or body dispatch.
3681
+
3682
+ Meta tools, native tools, Skills/resources and upward auth/input continuations
3683
+ compose registered native programs. SDK registration signatures and Promise
3684
+ shells remain unchanged. Current-method identity governs consumer dispatch;
3685
+ each original shell enters its own immutable program, so decorators, inherited
3686
+ delegates and replacements retain authority. Catalog policy failures reach
3687
+ native consumers as ordinary typed failures. Invocation still owns validation,
3688
+ classification, read-only enforcement, dispatch, write settlement and accounting.
3689
+ Its input and result callbacks select native programs while custom synchronous
3690
+ or Promise callbacks retain their original behavior.
3691
+
3692
+ The canonical `RequestScope` travels through SDK callback edges and registry
3693
+ views. A continuation's durable nonce claim still precedes dispatch, retains its
3694
+ caller, endpoint, arguments, TTL and round bindings, and cannot authorize another
3695
+ write after an ambiguous result. Retained classification and settlement facts
3696
+ never reopen a closed request. A finished program can still shape its response
3697
+ and retained recovery facts after its connector authority closes; the inbound
3698
+ request signal still cancels that response work. Neither path grants another
3699
+ dispatch. Agent-output redaction, withheld diagnostics,
3700
+ secret registration and result bounds retain their existing pure algorithms.
3701
+ `upwardStep` is only a typed generator adapter: it lets `authFlow` deliver the
3702
+ original ordinary failure to a local catch without converting interruption.
3703
+
3704
+ Concurrent authorization failures share one request-owned producer and Deferred
3705
+ answer per connector, including failures and the unknown-write advice variant.
3706
+ The existing `forkRequest` seam starts that producer under the request Scope;
3707
+ its Context contains only the canonical request owner and scheduler. A waiting
3708
+ call's budget, credentials and cancellation do not own the producer. Closing the
3709
+ request revokes it, and a closed request cannot consume or restart its handoff.
3710
+ Only `runtime/run.ts` starts fibers. Admission and downstream refresh/connection
3711
+ waits retain their deliberate Promise handoffs for workerd's waiting-request I/O
3712
+ context.
3713
+
3714
+ Intentional leaves are actual pinned SDK handler/transport/server operations,
3715
+ SDK schema and request-state codecs, platform body readers and cancellation,
3716
+ Web Crypto hashes, vault owner/seal/open/sign/verify methods and unregistered custom
3717
+ identity, credential, storage, executor and tool callbacks. Connecta's OAuth-link
3718
+ binding, TTL, progress, closing and one-use claims are native orchestration around
3719
+ those leaves. The supported protocol and unsupported-feature refusals remain
3720
+ unchanged. Native consumer, failure, decorator, sharing, close and minified-bundle
3721
+ regressions live in `upward-native.test.ts` and
3722
+ `upward-native-bundle.node.test.ts`; existing wire, continuation, redaction,
3723
+ physical-attempt and body/admission suites remain compatibility evidence.
3724
+
3725
+ The combined native/operator graph has a measured contributor bundle allowance
3726
+ in PR #1060: the UI gzip cap is 346,000 bytes, with its historical baseline
3727
+ unchanged. The official full checker measured 345,228 bytes after regenerating
3728
+ the merged UI assets. Removing unused Invocation/Catalog registration retention
3729
+ returned the public Worker entry from 198,744 to 77,426 bytes; live minified
3730
+ Context, inherited-method, decorator and restoration proofs retain native
3731
+ composition. Root, example Worker, public Worker, D1 and Clerk caps remain
3732
+ unchanged. This cost decision grants no later request-body-limit allowance.
3733
+
3734
+ ### Catalog operation vocabulary (contributor)
3735
+
3736
+ The standard hatch privately registers bounded vendor operation text against its
3737
+ ApiTool identity; `apiConnector` transfers those facts to the constructed
3738
+ connector. `operation-vocabulary.ts` contains only pure lexical/cache logic and
3739
+ types. Catalog discovery consumes those facts after loading and classifying a
3740
+ complete visible catalog. It never imports a provider or an operation index,
3741
+ parses operation contracts, invokes a handler, or authenticates. Matching requires
3742
+ all terms in a single operation and a visible destination hatch. Endpoint,
3743
+ connector and tool grants are checked on every query, outside the request-free
3744
+ cache. Exact authored names and complete authored matches precede API pointers;
3745
+ ordinary result limits, offsets, catalog failure disclosure and byte bounds apply.
3746
+ The published discovery contract states compilation, query and cache bounds.
3747
+ No request identity, signal, credential or promise enters a vocabulary cache.
3748
+
3749
+ ### Authenticated body-bound cost (contributor)
3750
+
3751
+ The separate #860 change has a measured allowance only for the example Worker
3752
+ gzip cap, from 785,000 to 786,000 bytes, with its historical baseline unchanged.
3753
+ The official full checker measures 785,036 bytes against D7's exact 784,948-byte
3754
+ lower graph, an actual 88-byte increase. It reuses the bounded platform body
3755
+ inspector, preserves native orchestration and avoids a new reader lifecycle.
3756
+ Root, UI, public Worker, D1, Clerk and asset caps remain fixed. The cost ruling
3757
+ does not approve protocol, cancellation or authority semantics.