@zackbart/connecta 0.28.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/CHANGELOG.md +855 -5
  2. package/README.md +125 -116
  3. package/bin/connecta.mjs +121 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/argument-redaction.js +230 -0
  12. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  13. package/dist/auth/clerk-sdk/client.js +5 -0
  14. package/dist/auth/clerk-sdk/generated.js +1845 -0
  15. package/dist/auth/clerk-transport.js +19 -0
  16. package/dist/auth/clerk.d.ts +17 -7
  17. package/dist/auth/clerk.js +189 -107
  18. package/dist/auth/cloudflare-access.js +25 -26
  19. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  20. package/dist/auth/downstream-client-metadata.js +133 -0
  21. package/dist/auth/downstream-oauth.js +1584 -1580
  22. package/dist/auth/downstream-revocation.js +106 -0
  23. package/dist/auth/oauth-token-response.js +231 -0
  24. package/dist/auth/oauth-v2-migration.js +116 -0
  25. package/dist/auth/static-oauth.js +93 -71
  26. package/dist/auth-elicitation.js +288 -0
  27. package/dist/authorization-handoff.js +111 -0
  28. package/dist/branding.d.ts +8 -16
  29. package/dist/branding.js +26 -59
  30. package/dist/byte-read-response.js +39 -0
  31. package/dist/call-admission.js +12 -16
  32. package/dist/catalog-cache.js +739 -0
  33. package/dist/catalog-drift.d.ts +50 -15
  34. package/dist/catalog-drift.js +365 -128
  35. package/dist/catalog-fingerprint.js +2 -4
  36. package/dist/catalog-freshness.js +13 -0
  37. package/dist/catalog-limits.js +2 -2
  38. package/dist/catalog-service.js +409 -225
  39. package/dist/catalog.js +43 -84
  40. package/dist/concurrency.js +3 -4
  41. package/dist/config-defaults.d.ts +39 -0
  42. package/dist/config-defaults.js +46 -0
  43. package/dist/config-schema.d.ts +181 -0
  44. package/dist/config-schema.js +435 -0
  45. package/dist/config-value-sources.js +102 -0
  46. package/dist/config.d.ts +633 -0
  47. package/dist/config.js +508 -0
  48. package/dist/connector-access.d.ts +1 -0
  49. package/dist/connector-access.js +13 -15
  50. package/dist/connector-caller.d.ts +8 -0
  51. package/dist/connector-caller.js +15 -6
  52. package/dist/connector-scope.js +1 -1
  53. package/dist/connectors/api-connector.d.ts +59 -7
  54. package/dist/connectors/api-connector.js +106 -19
  55. package/dist/connectors/api.js +4 -1
  56. package/dist/connectors/guarded-fetch.d.ts +144 -0
  57. package/dist/connectors/guarded-fetch.js +87 -38
  58. package/dist/connectors/negotiation-cache.js +46 -0
  59. package/dist/connectors/option-shapes.js +27 -0
  60. package/dist/connectors/remote-mcp.d.ts +30 -4
  61. package/dist/connectors/remote-mcp.js +1774 -320
  62. package/dist/connectors/resource-uri.js +335 -0
  63. package/dist/credential-contract.d.ts +10 -0
  64. package/dist/credential-rules.js +1 -3
  65. package/dist/credential-url.js +108 -0
  66. package/dist/credentials.d.ts +6 -1
  67. package/dist/credentials.js +27 -18
  68. package/dist/d1.d.ts +130 -0
  69. package/dist/d1.js +290 -0
  70. package/dist/describe-config.d.ts +165 -0
  71. package/dist/describe-config.js +315 -0
  72. package/dist/described.js +106 -0
  73. package/dist/downstream-input-context.js +128 -0
  74. package/dist/downstream-input.js +325 -0
  75. package/dist/errors.d.ts +132 -31
  76. package/dist/errors.js +175 -87
  77. package/dist/execute.js +798 -318
  78. package/dist/executor-admission.d.ts +1 -0
  79. package/dist/executor-admission.js +11 -15
  80. package/dist/executor-contract.d.ts +20 -0
  81. package/dist/executor-contract.js +48 -0
  82. package/dist/executor-result.js +30 -13
  83. package/dist/executors/quickjs-child.js +15 -9
  84. package/dist/executors/quickjs-protocol.js +5 -6
  85. package/dist/executors/quickjs-runtime.js +152 -59
  86. package/dist/executors/quickjs.js +49 -39
  87. package/dist/favicon.js +1 -2
  88. package/dist/fix-prompt.js +1 -3
  89. package/dist/guest-runtime.js +238 -0
  90. package/dist/guest-types.d.ts +218 -0
  91. package/dist/guest-types.js +1 -0
  92. package/dist/html-security.js +24 -0
  93. package/dist/identity.js +1 -3
  94. package/dist/inbound-credential.js +52 -0
  95. package/dist/index.d.ts +24 -256
  96. package/dist/index.js +65 -478
  97. package/dist/invocation-auth.js +69 -0
  98. package/dist/invocation.js +262 -79
  99. package/dist/json-schema.d.ts +1 -1
  100. package/dist/mcp-client-context.js +35 -0
  101. package/dist/mcp-result.js +11 -7
  102. package/dist/meta-output.js +61 -0
  103. package/dist/meta-tool-names.d.ts +2 -0
  104. package/dist/meta-tool-names.js +3 -0
  105. package/dist/meta-tools.js +419 -272
  106. package/dist/module-contracts.d.ts +8 -23
  107. package/dist/node.d.ts +0 -2
  108. package/dist/node.js +2 -2
  109. package/dist/oauth-callback-outcome.js +1 -3
  110. package/dist/oauth-handoff.js +194 -0
  111. package/dist/oauth-sealing.js +20 -0
  112. package/dist/operator-record.d.ts +137 -0
  113. package/dist/operator-record.js +420 -0
  114. package/dist/operator-ui/assets.js +31 -0
  115. package/dist/operator-ui/contract.d.ts +72 -0
  116. package/dist/operator-ui/contract.js +1 -0
  117. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  118. package/dist/operator-ui/generated.js +5 -4
  119. package/dist/operator-ui/model.d.ts +12 -87
  120. package/dist/operator-ui/model.js +2 -9
  121. package/dist/operator-ui/setup-commands.js +1 -3
  122. package/dist/operator-ui/view.d.ts +323 -0
  123. package/dist/operator-ui/view.js +62 -189
  124. package/dist/page-styles.js +2 -3
  125. package/dist/program-result.js +5 -0
  126. package/dist/program-source.js +14 -2
  127. package/dist/{exempt-writes.js → program-writes.js} +60 -19
  128. package/dist/provider.d.ts +142 -0
  129. package/dist/provider.js +187 -0
  130. package/dist/providers/_shared/google/result-size.js +57 -0
  131. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  132. package/dist/providers/_shared/google/service-account.js +500 -0
  133. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  134. package/dist/providers/_shared/google/workspace.js +518 -0
  135. package/dist/providers/_shared/request-token.js +83 -0
  136. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  137. package/dist/providers/basecamp/index.js +997 -0
  138. package/dist/providers/basecamp/skill.generated.js +9 -0
  139. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  140. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  141. package/dist/providers/breeze/skill.generated.js +9 -0
  142. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  143. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  144. package/dist/providers/ccb/skill.generated.js +9 -0
  145. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  146. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  147. package/dist/providers/cloudflare/skill.generated.js +10 -0
  148. package/dist/providers/docs/index.d.ts +54 -0
  149. package/dist/providers/docs/index.js +1237 -0
  150. package/dist/providers/docs/skill.generated.js +9 -0
  151. package/dist/providers/drive/index.d.ts +53 -0
  152. package/dist/providers/drive/index.js +1523 -0
  153. package/dist/providers/drive/skill.generated.js +9 -0
  154. package/dist/providers/forms/index.d.ts +54 -0
  155. package/dist/providers/forms/index.js +1217 -0
  156. package/dist/providers/forms/skill.generated.js +11 -0
  157. package/dist/providers/github/auth.d.ts +32 -0
  158. package/dist/providers/github/auth.js +361 -0
  159. package/dist/providers/github/hosted.js +218 -0
  160. package/dist/providers/github/index.d.ts +13 -0
  161. package/dist/providers/github/index.js +140 -0
  162. package/dist/providers/github/key.js +100 -0
  163. package/dist/providers/github/rest.js +321 -0
  164. package/dist/providers/github/scope.d.ts +29 -0
  165. package/dist/providers/github/scope.js +124 -0
  166. package/dist/providers/github/skill.generated.js +8 -0
  167. package/dist/providers/gmail/index.d.ts +51 -0
  168. package/dist/providers/gmail/index.js +1436 -0
  169. package/dist/providers/gmail/skill.generated.js +9 -0
  170. package/dist/providers/infisical/index.d.ts +7 -0
  171. package/dist/providers/infisical/index.js +713 -0
  172. package/dist/providers/infisical/skill.generated.js +8 -0
  173. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  174. package/dist/providers/linear/index.js +352 -0
  175. package/dist/providers/linear/skill.generated.js +8 -0
  176. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  177. package/dist/providers/mixpanel/index.js +416 -0
  178. package/dist/providers/mixpanel/skill.generated.js +9 -0
  179. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  180. package/dist/providers/{notion.js → notion/index.js} +226 -239
  181. package/dist/providers/notion/skill.generated.js +10 -0
  182. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  183. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  184. package/dist/providers/overflow/skill.generated.js +8 -0
  185. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  186. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  187. package/dist/providers/planning-center/skill.generated.js +10 -0
  188. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  189. package/dist/providers/revenuecat/index.js +557 -0
  190. package/dist/providers/revenuecat/skill.generated.js +11 -0
  191. package/dist/providers/sheets/index.d.ts +54 -0
  192. package/dist/providers/sheets/index.js +1237 -0
  193. package/dist/providers/sheets/skill.generated.js +10 -0
  194. package/dist/providers/slides/index.d.ts +53 -0
  195. package/dist/providers/slides/index.js +2645 -0
  196. package/dist/providers/slides/skill.generated.js +9 -0
  197. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  198. package/dist/providers/stripe/index.js +266 -0
  199. package/dist/providers/stripe/skill.generated.js +11 -0
  200. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  201. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  202. package/dist/providers/tithely/skill.generated.js +8 -0
  203. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  204. package/dist/providers/vercel/index.js +1038 -0
  205. package/dist/providers/vercel/mcp-ownership.js +212 -0
  206. package/dist/providers/vercel/skill.generated.js +11 -0
  207. package/dist/registry.d.ts +110 -171
  208. package/dist/registry.js +588 -927
  209. package/dist/request-state.js +21 -0
  210. package/dist/resource-read.js +103 -0
  211. package/dist/result-shapes.js +17 -49
  212. package/dist/retired-toolkits.js +1 -1
  213. package/dist/routes/access-tokens.js +3 -6
  214. package/dist/routes/activity-context.js +21 -0
  215. package/dist/routes/activity-disclosure.js +40 -0
  216. package/dist/routes/activity.js +59 -21
  217. package/dist/routes/actor-labels.js +3 -50
  218. package/dist/routes/connect.js +221 -0
  219. package/dist/routes/credentials.js +15 -27
  220. package/dist/routes/mcp.js +365 -130
  221. package/dist/routes/oauth-client-metadata.js +15 -0
  222. package/dist/routes/oauth-management.js +45 -107
  223. package/dist/routes/oauth.js +110 -57
  224. package/dist/routes/operator.js +3 -3
  225. package/dist/routes/shared.d.ts +25 -64
  226. package/dist/routes/shared.js +160 -80
  227. package/dist/routes/skills.js +30 -0
  228. package/dist/routes/ui-config.js +225 -0
  229. package/dist/routes/ui-data.js +40 -35
  230. package/dist/routes/ui.js +66 -56
  231. package/dist/runtime/admission.js +4 -2
  232. package/dist/runtime/call-admission.js +3 -1
  233. package/dist/runtime/connector-scope.js +1 -4
  234. package/dist/runtime/run.js +10 -39
  235. package/dist/runtime/services.js +12 -117
  236. package/dist/runtime/shared-read.js +1 -3
  237. package/dist/runtime/storage.js +8 -1
  238. package/dist/sent-secrets.d.ts +93 -0
  239. package/dist/sent-secrets.js +1013 -0
  240. package/dist/server.js +28 -71
  241. package/dist/skills.js +416 -174
  242. package/dist/sqlite.d.ts +48 -0
  243. package/dist/sqlite.js +181 -0
  244. package/dist/storage/keys.js +443 -0
  245. package/dist/storage/memory.js +16 -4
  246. package/dist/storage/sql.d.ts +66 -0
  247. package/dist/storage/sql.js +543 -0
  248. package/dist/timeout.js +4 -2
  249. package/dist/tool-name.js +17 -0
  250. package/dist/tool-safety.d.ts +9 -35
  251. package/dist/tool-safety.js +20 -40
  252. package/dist/types.d.ts +336 -56
  253. package/dist/types.js +0 -1
  254. package/dist/ui.d.ts +7 -19
  255. package/dist/ui.js +76 -90
  256. package/dist/url-safety.js +3 -6
  257. package/dist/usage-guide.js +146 -0
  258. package/dist/validate.d.ts +7 -0
  259. package/dist/validate.js +309 -47
  260. package/dist/version.d.ts +1 -7
  261. package/dist/version.js +3 -7
  262. package/dist/worker.d.ts +11 -0
  263. package/dist/worker.js +435 -0
  264. package/documentation/README.md +63 -0
  265. package/documentation/architecture.md +696 -231
  266. package/documentation/auth.md +1069 -546
  267. package/documentation/code-mode.md +662 -356
  268. package/documentation/deploying.md +146 -0
  269. package/documentation/integrating.md +159 -0
  270. package/documentation/meta-tools.md +570 -216
  271. package/documentation/operating.md +135 -0
  272. package/documentation/operator-tests.md +97 -0
  273. package/documentation/operator-ui.md +132 -0
  274. package/documentation/provider-migration-0.29.md +65 -0
  275. package/examples/worker/AGENTS.md +34 -12
  276. package/examples/worker/README.md +293 -255
  277. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  278. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  279. package/examples/worker/src/connecta.config.ts +151 -0
  280. package/examples/worker/src/index.ts +14 -197
  281. package/examples/worker/wrangler.jsonc +15 -35
  282. package/package.json +126 -54
  283. package/templates/node/.dockerignore +1 -2
  284. package/templates/node/.env.example +20 -21
  285. package/templates/node/AGENTS.md +21 -17
  286. package/templates/node/Dockerfile +3 -5
  287. package/templates/node/README.md +140 -58
  288. package/templates/node/docker-compose.yml +11 -12
  289. package/templates/node/package.json +4 -2
  290. package/templates/node/src/connecta.config.ts +119 -0
  291. package/templates/node/src/index.ts +9 -154
  292. package/templates/node/src/provision-token.ts +17 -0
  293. package/AGENTS.md +0 -170
  294. package/dist/artifacts/connector.d.ts +0 -58
  295. package/dist/artifacts/connector.js +0 -730
  296. package/dist/artifacts/document.js +0 -86
  297. package/dist/artifacts/guide.js +0 -140
  298. package/dist/artifacts/html-scan.js +0 -329
  299. package/dist/artifacts/json.js +0 -78
  300. package/dist/artifacts/kv-store.d.ts +0 -20
  301. package/dist/artifacts/kv-store.js +0 -140
  302. package/dist/artifacts/markdown.js +0 -478
  303. package/dist/artifacts/module.d.ts +0 -37
  304. package/dist/artifacts/module.js +0 -66
  305. package/dist/artifacts/operations.d.ts +0 -197
  306. package/dist/artifacts/operations.js +0 -1002
  307. package/dist/artifacts/refresh.d.ts +0 -55
  308. package/dist/artifacts/refresh.js +0 -236
  309. package/dist/artifacts/routes.js +0 -206
  310. package/dist/artifacts/types.d.ts +0 -214
  311. package/dist/artifacts/types.js +0 -22
  312. package/dist/artifacts/validate.d.ts +0 -62
  313. package/dist/artifacts/validate.js +0 -651
  314. package/dist/artifacts.d.ts +0 -6
  315. package/dist/artifacts.js +0 -9
  316. package/dist/auth/bearer.d.ts +0 -11
  317. package/dist/auth/bearer.js +0 -50
  318. package/dist/providers/basecamp.js +0 -445
  319. package/dist/providers/linear.js +0 -217
  320. package/dist/providers/mixpanel.js +0 -247
  321. package/dist/providers/revenuecat.js +0 -317
  322. package/dist/providers/stripe.js +0 -253
  323. package/dist/providers/vercel.js +0 -1253
  324. package/dist/storage/file.d.ts +0 -15
  325. package/dist/storage/file.js +0 -455
  326. package/ethos.md +0 -93
  327. package/examples/worker/src/cloudflare-kv.ts +0 -43
  328. package/examples/worker/src/d1-activity-row.ts +0 -155
  329. package/examples/worker/src/d1-activity.ts +0 -147
  330. package/examples/worker/src/d1-storage.ts +0 -138
  331. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  332. package/templates/node/src/file-activity.ts +0 -182
@@ -1,9 +1,17 @@
1
1
  # connecta — Cloudflare Worker example
2
2
 
3
+ Agent setup guide for the Worker deployment.
4
+ For deployment choices and upgrades, use [Deploying Connecta](https://github.com/zackbart/connecta/blob/main/documentation/deploying.md).
5
+ For connector changes, use [Integrating services](https://github.com/zackbart/connecta/blob/main/documentation/integrating.md).
6
+ The [agent index](https://github.com/zackbart/connecta/blob/main/documentation/README.md) routes endpoint use and package
7
+ maintenance. This README and [local agent instructions](./AGENTS.md) own the
8
+ runnable Worker steps.
9
+
3
10
  A deployable Worker that aggregates a downstream remote MCP and an in-code HTTP
4
- API connector, guarded by Cloudflare Access, with state in a KV namespace. Its
5
- required Worker Loader binding backs the seven-tool surface and requires the
6
- Workers Paid plan.
11
+ API connector, guarded by Cloudflare Access. It uses three Cloudflare resources
12
+ and nothing else: one D1 database (`CONNECTA_DB`) for every piece of state, the
13
+ Worker Loader binding (`LOADER`) behind the six-tool surface, which requires
14
+ the Workers Paid plan, and one secret (`CREDENTIAL_ENCRYPTION_KEY`).
7
15
 
8
16
  This is also the **starting template for a deployment**: a real deployment
9
17
  should be its own repository that pins an exact `@zackbart/connecta` version and
@@ -12,15 +20,12 @@ migrations, and secrets.
12
20
 
13
21
  ## Files
14
22
 
15
- | File | What it is |
16
- | --- | --- |
17
- | `src/index.ts` | the Worker entrypoint — connector and auth configuration |
18
- | `src/cloudflare-kv.ts` | `KVStorage` over Workers KV (deployment-owned, not a package export) |
19
- | `src/d1-storage.ts` | `KVStorage` over D1 with atomic compare-and-set (optional; see below) |
20
- | `src/d1-activity.ts` | `ActivityStore` over D1 (deployment-owned; see below) |
21
- | `src/d1-activity-row.ts` | the row ↔ event mapping `d1-activity.ts` uses, including friction derived from `error_code` for rows written before that column existed |
22
- | `src/r2-artifact-blobs.ts` | artifact bodies in an R2 bucket, beside a D1-backed `kvArtifactStore` (optional) |
23
- | `wrangler.jsonc` | Worker name, vars, bindings, `compatibility_flags` |
23
+ | File | What it is |
24
+ | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
25
+ | `src/index.ts` | the Worker entrypoint — starts the configuration, under 30 lines |
26
+ | `src/connecta.config.ts` | connectors, auth, storage, and optional modules, as `defineConfig((env) => …)` |
27
+ | `wrangler.jsonc` | Worker name, vars, bindings, `compatibility_flags` |
28
+ | `scripts/copy-kv-to-d1.mjs`, `kv-to-d1.wrangler.jsonc` | one-shot copy of a 0.28 Workers KV deployment's state into D1, run once and deleted ([Upgrading from 0.28](#upgrading-from-028)) |
24
29
 
25
30
  Keep `enable_request_signal` in `wrangler.jsonc`. On Workers it lets a live
26
31
  response's client disconnect abort its request, so connecta cancels the stream
@@ -30,13 +35,10 @@ on a later request if workerd ends one without delivering a signal or stream
30
35
  cancellation. Set it above the longest authorization, tool call, and response
31
36
  delivery your deployment expects to serve.
32
37
 
33
- `cloudflare-kv.ts`, `d1-storage.ts`, and `d1-activity.ts` deliberately live
34
- here rather than in the package: storage backends are deployment-owned, so the
35
- package ships only the generic `KVStorage` and `ActivityStore` contracts.
36
- Workers KV is eventually consistent across locations; use a strongly consistent
37
- `KVStorage` adapter, such as [`d1-storage.ts`](#strongly-consistent-storage-optional),
38
- when OAuth disconnect or credential rotation must become globally visible
39
- immediately.
38
+ Storage is `d1Storage(env.CONNECTA_DB)` from `@zackbart/connecta/d1`, and
39
+ activity history, when `CONNECTA_ACTIVITY` is `"on"`, is `d1ActivityStore` over
40
+ the same database.
41
+ See [Storage](#storage).
40
42
 
41
43
  ## Deploy
42
44
 
@@ -46,7 +48,7 @@ package from the repository root.
46
48
  ```sh
47
49
  npm install # from the package root
48
50
 
49
- wrangler kv namespace create CONNECTA_KV # paste the id into wrangler.jsonc
51
+ wrangler d1 create connecta # paste the id into wrangler.jsonc
50
52
 
51
53
  cd examples/worker
52
54
  wrangler secret put DOWNSTREAM_TOKEN
@@ -79,8 +81,10 @@ attempt and add the narrowest matching entry rather than allowing its entire
79
81
  origin.
80
82
 
81
83
  Access then serves OAuth discovery and turns the client's opaque token into the
82
- trusted `ctx.access` identity connecta reads. A cron job or CI client uses an
83
- Access service token instead.
84
+ trusted human `ctx.access` identity connecta reads. A cron job or CI client
85
+ needs Access service headers to cross the edge and a stored `cta_` bearer to
86
+ authenticate inside connecta. An Access service identity alone is refused.
87
+ See [Machine tokens](#machine-tokens).
84
88
 
85
89
  Through the API, the relevant part of the application is:
86
90
 
@@ -115,8 +119,8 @@ on a deployed Worker's production identity.
115
119
  ### Optional resource management with Alchemy
116
120
 
117
121
  Keep this example's Worker on Wrangler. A copied deployment may use
118
- [Alchemy](https://alchemy.run) to manage its KV namespace, optional D1 databases
119
- and R2 bucket, and the Access application without adding Alchemy to connecta or
122
+ [Alchemy](https://alchemy.run) to manage its D1 database, optional R2 bucket,
123
+ and the Access application without adding Alchemy to connecta or
120
124
  changing this template. In that deployment, install `alchemy@2.0.0-beta.79`
121
125
  and `effect@4.0.0` at exact versions. The latter is connecta's own
122
126
  Effect pin; check `npm ls effect` for one resolved copy after installation.
@@ -135,14 +139,9 @@ export default Alchemy.Stack(
135
139
  "ConnectaResources",
136
140
  { providers: Cloudflare.providers(), state: Cloudflare.state() },
137
141
  Effect.gen(function* () {
138
- const kv = yield* Cloudflare.KV.Namespace("ConnectaKV", { title: "connecta-kv" }).pipe(adopt(true));
139
- const activity = yield* Cloudflare.D1.Database("ActivityDB", {
140
- name: "connecta-activity", migrations: "./migrations/activity",
141
- }).pipe(adopt(true));
142
- const storage = yield* Cloudflare.D1.Database("StorageDB", {
143
- name: "connecta-storage", migrations: "./migrations/storage",
142
+ const database = yield* Cloudflare.D1.Database("ConnectaDB", {
143
+ name: "connecta",
144
144
  }).pipe(adopt(true));
145
- const blobs = yield* Cloudflare.R2.Bucket("ArtifactBlobs", { name: "connecta-artifacts" }).pipe(adopt(true));
146
145
  const access = yield* Cloudflare.Access.Application("ConnectaAccess", {
147
146
  type: "self_hosted",
148
147
  name: "Connecta",
@@ -160,42 +159,34 @@ export default Alchemy.Stack(
160
159
  },
161
160
  },
162
161
  }).pipe(adopt(true));
163
- return { kvId: kv.namespaceId, activityId: activity.databaseId,
164
- storageId: storage.databaseId, bucketName: blobs.bucketName,
162
+ return { databaseId: database.databaseId,
165
163
  accessId: access.applicationId };
166
164
  }),
167
165
  );
168
166
  ```
169
167
 
170
168
  Replace the Worker ID and example Access policy before applying this stack.
171
- Keep only the resources the deployment uses: `STORAGE_DB` replaces
172
- `CONNECTA_KV`; `ACTIVITY_DB` and R2 are optional. Copy the output IDs into the
173
- matching `wrangler.jsonc` bindings. Keep `LOADER` in `worker_loaders` and, when
174
- enabled, the Browser Rendering binding and cron trigger in the Wrangler-owned
175
- Worker configuration. Configure the main and artifact hostnames as Wrangler
176
- custom domain `routes` with `custom_domain: true`. The Worker-level Access
177
- destination covers both hostnames; verify both against the same application
178
- after domain setup. Continue to set secrets with `wrangler secret put` and to
179
- deploy the Worker with Wrangler. After its initial `wrangler deploy`, use
180
- `wrangler versions upload` and `wrangler versions deploy` for code updates;
181
- apply route and cron changes through Wrangler too.
169
+ `CONNECTA_DB` is required. Copy the database ID into `wrangler.jsonc` and keep
170
+ `LOADER` in `worker_loaders`. Configure the hostname as a Wrangler custom domain
171
+ route with `custom_domain: true`. Verify it against the Worker-level Access
172
+ application, set secrets with `wrangler secret put`, and deploy with Wrangler.
173
+ After the initial `wrangler deploy`, use `wrangler versions upload` and
174
+ `wrangler versions deploy` for code updates.
182
175
 
183
176
  The `adopt(true)` calls explicitly permit taking over matching existing
184
177
  resources; compare their live names, IDs, Access policy, and data before the
185
- first Alchemy deploy. Remove adoption where a resource is new. Put the D1 SQL
186
- from this README in the shown migration directories before deploying. With
187
- `migrations` configured, Alchemy copies an adopted database's Wrangler
188
- migration history once into `__alchemy_migrations`; the old history stays
189
- frozen. Review pending SQL before handing migrations over. Remote
178
+ first Alchemy deploy. Remove adoption where a resource is new. The stack
179
+ configures no `migrations`: connecta creates its tables on first use. Remote
190
180
  `Cloudflare.state()` bootstraps an account-level state-store Worker backed by a
191
181
  Durable Object and Secrets Store entries for its credentials. Retain them:
192
182
  deleting the state store loses Alchemy's record of what it owns.
193
183
 
194
- Verification for this optional path stops at the API and types. The fenced
195
- snippet was copied to an isolated `alchemy.run.ts` and passed `tsc --noEmit`
196
- with TypeScript 5.9.3, Alchemy 2.0.0-beta.79, and Effect 4.0.0, with
197
- `npm ls effect` confirming one resolved copy. No Alchemy deploy or adoption
198
- was run.
184
+ Verification for this optional path stops at the API and types. The 0.28
185
+ form of this snippet (a KV namespace and two D1 databases with `migrations`)
186
+ was copied to an isolated `alchemy.run.ts` and passed `tsc --noEmit` with
187
+ TypeScript 5.9.3, Alchemy 2.0.0-beta.79, and Effect 4.0.0, with `npm ls
188
+ effect` confirming one resolved copy; this form only removes resources and the
189
+ optional `migrations` field. No Alchemy deploy or adoption was run.
199
190
  [Alchemy's Worker resource](https://alchemy.run/cloudflare/compute/workers/)
200
191
  can declare `WorkerLoader`, domains, Browser Rendering, and cron wiring, but
201
192
  those properties are coupled to its script deploy. Its current full deploy
@@ -233,15 +224,17 @@ Then point an MCP client at `<PUBLIC_URL>/mcp`. The example explicitly enables
233
224
  authentication controls. The page labels tool safety, offers fixed repair
234
225
  prompts for classified failures, and shows client setup commands for endpoints
235
226
  the signed-in person may use. Those commands contain no token. There is no
236
- separate Credentials or Tokens tab.
227
+ separate Credentials tab. The Access tokens page is available to permitted
228
+ interactive operators.
237
229
 
238
230
  ## Select optional modules
239
231
 
240
232
  `cloudflareAccessAuth()` reads trusted identity after Access admits the Worker
241
- request. Humans may use their code-derived connector view; service identities
242
- can use MCP but cannot manage personal or shared auth as an interactive human.
233
+ request. Humans may use their code-derived connector view and operator pages.
234
+ Machines authenticate with `accessTokens(storage)` over the same D1 database;
235
+ Access service headers alone never grant MCP access or interactive authority.
243
236
  Keep users, groups, and admission in Access. Connector visibility and management
244
- permissions belong in `src/index.ts`.
237
+ permissions belong in `src/connecta.config.ts`.
245
238
 
246
239
  `identity.connectorAccess` selects discoverable and callable connectors.
247
240
  `credentialAdministration` separately allows shared credential and OAuth
@@ -250,8 +243,52 @@ account. Both management permissions default to none. Grant the intended
250
243
  owner's shared permissions explicitly, and grant users personal permissions
251
244
  only for connectors configured with `authScope: "personal"`. Static headers
252
245
  remain deployment configuration. `activityAccess` governs global history reads.
246
+ `accessTokenManagement` separately allows interactive operators to create, rename,
247
+ and revoke machine tokens. This example grants that permission to every signed-in
248
+ human, matching its connector-management permissions; narrow all three for a team.
253
249
  See [inbound identity](../../documentation/auth.md#principals-visibility-and-operators).
254
250
 
251
+ ### Machine tokens
252
+
253
+ `accessTokens(storage)` is always installed over `CONNECTA_DB`, with no new
254
+ binding. Empty token storage admits no machine client. A human authenticated
255
+ through Access can still sign in and bootstrap the first token. Open
256
+ `<PUBLIC_URL>/`, select Access tokens, and create a named token. Save the
257
+ returned `cta_` secret privately; it is returned once. UI-issued tokens retain
258
+ the issuing human's principal, while connector and pool rules are evaluated
259
+ on every request. They never grant interactive operator authority.
260
+
261
+ A machine sends all three headers:
262
+
263
+ ```text
264
+ CF-Access-Client-Id: <access-service-client-id>
265
+ CF-Access-Client-Secret: <access-service-client-secret>
266
+ Authorization: Bearer <stored-cta-token>
267
+ ```
268
+
269
+ Permit the service credentials in the Worker-level Access application's policy.
270
+ The service headers admit the request at the edge; connecta verifies the bearer
271
+ against D1. Neither credential works alone for machine access. Doctor uses
272
+ the same three credentials; see [Client authentication and activity](#client-authentication-and-activity).
273
+
274
+ For programmatic bootstrap, trusted provisioning code that already holds this
275
+ deployment's D1 binding can use the published API against that same storage:
276
+
277
+ ```ts
278
+ import { AccessTokenManager } from "@zackbart/connecta/auth/access-tokens";
279
+ import { d1Storage } from "@zackbart/connecta/d1";
280
+
281
+ const manager = new AccessTokenManager(d1Storage(env.CONNECTA_DB));
282
+ const { token } = await manager.create("ci-client", "trusted-provisioning");
283
+ // Deliver token once to the client's secret store, outside logs and source control.
284
+ ```
285
+
286
+ This creates a token without a human principal. The name is a label, never a
287
+ grant. Provision only through trusted operator tooling; do not expose this
288
+ code as a public route or mint a token at startup. Existing unrevoked `cta_`
289
+ tokens survive a state migration into this same D1 namespace. Keep
290
+ `accessTokens(storage)` configured and preserve the identity and pool rules.
291
+
255
292
  ### UI and encrypted credentials
256
293
 
257
294
  Import `operatorUi` from `@zackbart/connecta/ui` and set `ui: operatorUi()`.
@@ -270,37 +307,41 @@ the secret is configured. Keep this base64 32-byte key in Worker secrets:
270
307
  node -e "console.log(crypto.randomBytes(32).toString('base64'))"
271
308
  ```
272
309
 
273
- Reuse the same key and KV namespace during upgrades. Without the secret, omit
310
+ Reuse the same key and D1 database during upgrades. Without the secret, omit
274
311
  the vault; declared credential slots remain unmanageable. Keep the key outside
275
- KV because it protects a copied namespace. Credential replacement takes effect
312
+ D1 because it protects a copied database. Credential replacement takes effect
276
313
  on the next call without redeploying; no liveness probe runs in the background.
277
314
  With this vault, 0.25.0 seals existing downstream OAuth state on first read.
278
315
  Rolling back to 0.24.x then requires reauthorizing those OAuth connectors.
279
316
 
280
317
  The shipped Notion connector uses a deployment-owned static header and echo
281
318
  needs no secret. To exercise vault controls, declare a `credential` slot on an
282
- `api()` connector or use a provider such as `notion()` that declares its own.
319
+ `api()` connector or use Notion's explicit `surface: "api"` integration
320
+ interface, which declares its own slot.
283
321
  Authorized users manage that slot inside the connection. Configuring a vault
284
322
  does not create credentials or permissions by itself.
285
323
 
286
324
  ### Client authentication and activity
287
325
 
288
- Interactive MCP clients use Access Managed OAuth. Unattended clients use Access
289
- service tokens when needed. Connecta-issued `cta_` tokens and their management
290
- routes are removed; a configured Connecta bearer cannot cross the Access edge
291
- alone.
326
+ Interactive MCP clients use Access Managed OAuth. Machine clients need both
327
+ Access service credentials and a stored `cta_` bearer, provisioned as described
328
+ in [Machine tokens](#machine-tokens). `accessTokens(storage)` is always enabled,
329
+ and the example grants token management to every interactive Access operator.
330
+ Keep token, connector, and pool grants in deployment code.
292
331
 
293
- Activity uses `activityHistory({ store: d1ActivityStore(env.ACTIVITY_DB) })`
294
- from `@zackbart/connecta/activity`. Enable the database and bindings described
295
- below. Omit the module and store wiring to record no history and show no
296
- Activity tab. Diagnostics remain independent; `logger: "silent"` suppresses
332
+ Activity uses `activityHistory({ store: d1ActivityStore(env.CONNECTA_DB) })`,
333
+ with `activityHistory` from `@zackbart/connecta/activity` and `d1ActivityStore`
334
+ from `@zackbart/connecta/d1`. See [Activity history](#activity-history-optional).
335
+ Omit the module and store wiring to record no history and show no Activity tab. Diagnostics remain independent; `logger: "silent"` suppresses
297
336
  them explicitly.
298
337
 
299
- Verify MCP health and the exact seven tools with:
338
+ Verify MCP health and the exact six tools with:
300
339
 
301
340
  ```sh
302
- CF_ACCESS_CLIENT_ID=… CF_ACCESS_CLIENT_SECRET=… \
303
- npx connecta doctor --url "$PUBLIC_URL"
341
+ CF_ACCESS_CLIENT_ID='REPLACE_WITH_ACCESS_CLIENT_ID' \
342
+ CF_ACCESS_CLIENT_SECRET='REPLACE_WITH_ACCESS_CLIENT_SECRET' \
343
+ CONNECTA_TOKEN='cta_REPLACE_WITH_STORED_SECRET' \
344
+ connecta doctor --url "$PUBLIC_URL"
304
345
  ```
305
346
 
306
347
  Doctor reports the configured `DynamicWorkerExecutor`, not a presumed Node
@@ -320,197 +361,194 @@ The required Worker Loader binding is checked into `wrangler.jsonc`:
320
361
  "worker_loaders": [{ "binding": "LOADER" }]
321
362
  ```
322
363
 
323
- `src/index.ts` constructs `DynamicWorkerExecutor` with only `env.LOADER` and
324
- serves the seven-tool surface. Do not add `bindings`, `modules`, or
364
+ `src/connecta.config.ts` uses `workerExecutor({ loader: env.LOADER })` from
365
+ `@zackbart/connecta/worker`. The adapter constructs the upstream
366
+ `DynamicWorkerExecutor` with only the loader and a deadline, and disposes each
367
+ run's loader and RPC handles when its lease ends, even if the guest has not
368
+ settled. It serves the six-tool surface. Do not add `bindings`, `modules`, or
325
369
  `globalOutbound`; they grant guest code ambient authority. A copied deployment
326
370
  owns the package install — see
327
371
  [copied into its own repository](#copied-into-its-own-repository).
328
372
 
329
- ## Strongly consistent storage (optional)
330
-
331
- `KVStorage` has an optional atomic `compareAndSet`, for a subsystem that must
332
- claim a key exactly once: artifacts commit every write by it, issuing access
333
- tokens requires it, and an OAuth handoff binds its owner with it when it is
334
- there. **Workers KV cannot provide it.** It is eventually
335
- consistent: two locations can each read a key as absent and both write, so
336
- `cloudflare-kv.ts` declares no `compareAndSet` rather than fake one with a read
337
- followed by a write. `src/d1-storage.ts` is a complete `KVStorage` over D1
338
- that does provide it, one SQL statement per claim, with TTLs honored at read
339
- time. It replaces the KV namespace rather than sitting beside it: every piece
340
- of connecta state lives in one store.
341
-
342
- 1. Create the database and uncomment the `STORAGE_DB` entry of the
343
- `d1_databases` binding in `wrangler.jsonc`, pasting in the id it prints:
344
-
345
- ```sh
346
- wrangler d1 create connecta-storage
347
- ```
348
-
349
- 2. Apply the schema (keep it in a deployment-owned `migrations/` directory):
350
-
351
- ```sql
352
- CREATE TABLE IF NOT EXISTS connecta_kv (
353
- key TEXT PRIMARY KEY,
354
- value TEXT NOT NULL,
355
- expires_at_ms INTEGER
356
- );
357
-
358
- CREATE INDEX IF NOT EXISTS connecta_kv_expiry
359
- ON connecta_kv (expires_at_ms);
360
- ```
361
-
362
- 3. In `src/index.ts`, add `STORAGE_DB: D1Database` to `Env` and build the store
363
- from it:
364
-
365
- ```ts
366
- import { d1Storage } from "./d1-storage.js";
367
-
368
- const storage = d1Storage(env.STORAGE_DB);
369
- ```
373
+ When upgrading an existing deployment, replace
374
+ `import { DynamicWorkerExecutor } from "@cloudflare/codemode";` with
375
+ `import { workerExecutor } from "@zackbart/connecta/worker";` and use
376
+ `executor: workerExecutor({ loader: env.LOADER })` instead of
377
+ `executor: new DynamicWorkerExecutor({ loader: env.LOADER })`. Keep
378
+ `@cloudflare/codemode` installed as the optional peer. Direct upstream
379
+ construction now throws at boot with this migration instruction; its private
380
+ loader cannot release request-owned handles through Connecta's generic executor
381
+ wrapper.
382
+
383
+ ## Storage
384
+
385
+ Every piece of connecta state lives in the one D1 database bound as
386
+ `CONNECTA_DB`: downstream OAuth grants, sealed vault credentials, catalogs,
387
+ result paging, OAuth handoffs, access tokens, and activity.
388
+ `d1Storage` creates its `connecta_kv` table on first use, and
389
+ `d1ActivityStore` its `tool_call_activity` table, so there is no schema to
390
+ apply. Every compare-and-set is one SQL statement, which D1 serializes on the
391
+ database's primary: two isolates claiming the same key cannot both win.
392
+
393
+ For reference, the tables connecta creates:
394
+
395
+ ```sql
396
+ CREATE TABLE IF NOT EXISTS connecta_kv (
397
+ key TEXT PRIMARY KEY,
398
+ value TEXT NOT NULL,
399
+ expires_at_ms INTEGER
400
+ );
401
+ CREATE INDEX IF NOT EXISTS connecta_kv_expiry ON connecta_kv (expires_at_ms);
402
+ -- tool_call_activity: one row per completed call, keyset-paged on
403
+ -- (occurred_at_ms, id); see src/storage/sql.ts in the package.
404
+ ```
370
405
 
371
- Switching an existing deployment starts from empty state: downstream OAuth
372
- connections must be re-authorized and vault credentials re-entered, because
373
- nothing copies them out of the KV namespace.
406
+ ### Upgrading from 0.28
407
+
408
+ 0.28 shipped three storage shapes in this example. Each moves to the one
409
+ `CONNECTA_DB` binding:
410
+
411
+ - **Already on `d1-storage.ts`** (one D1 database, possibly also holding
412
+ activity). The table is unchanged, so no data moves. Rename the binding in
413
+ `wrangler.jsonc` to `CONNECTA_DB`, keeping `database_name` and
414
+ `database_id`, replace the copied `d1-storage.ts` / `d1-activity.ts` imports
415
+ with `@zackbart/connecta/d1`, and delete those files. A deployment that
416
+ called `pruneActivity` from a cron drops that call: retention is
417
+ `d1ActivityStore(db, { retentionDays })`. A binding rename
418
+ changes only the name the Worker sees; the database and its rows stay. An
419
+ existing `tool_call_activity` table missing `actor_namespace`, `friction`,
420
+ `approval`, `client_name`, `client_version`, or `package_version` gets nullable
421
+ columns added on first use. Old rows keep unknown package and client facts;
422
+ new rows record the actual package version and validated request client facts.
423
+ - **Activity and storage in two databases** (`ACTIVITY_DB` and `STORAGE_DB`).
424
+ Bind the storage database as `CONNECTA_DB`. Activity is history: either
425
+ start it fresh in `CONNECTA_DB`, or copy it once, before deploying 0.29 (so
426
+ the target has no `tool_call_activity` table yet), with
427
+ `wrangler d1 export connecta-activity --remote --table tool_call_activity --output activity.sql`
428
+ and `wrangler d1 execute connecta-storage --remote --file activity.sql`. Then
429
+ remove the `ACTIVITY_DB` binding.
430
+ - **Workers KV** (`CONNECTA_KV` and `cloudflare-kv.ts`). Workers KV is no longer
431
+ supported: `createConnecta` refuses storage without `compareAndSet` at boot.
432
+ Copy its state into D1 once, as described below, so OAuth grants, vault
433
+ credentials, and `cta_` access tokens keep working.
434
+
435
+ #### Copying Workers KV state into D1
436
+
437
+ Storage keys did not change in 0.29. `copyKvToD1(kv, db, { source })` from
438
+ `@zackbart/connecta/d1` preserves live keys, exact values including vault
439
+ ciphertext, and absolute expiries. The source is the KV namespace id. Counts
440
+ and failures name only families, never keys or values. Oversized UTF-8 strings
441
+ or rows are skipped and counted as invalid against D1's
442
+ [2,000,000-byte limit](https://developers.cloudflare.com/d1/platform/limits/).
443
+ A different live D1 value or expiry is a conflict, kept by default.
444
+
445
+ Use a maintenance window. Copying while either deployment serves can restore
446
+ revoked tokens, stale refresh tokens, or disconnected connectors. A rerun is
447
+ safe only while the source remains stable and D1 has no new application writes.
448
+ The script reads the source id from `kv-to-d1.wrangler.jsonc` and checks two
449
+ consecutive listing/hash passes before a copy or verification pass. The config
450
+ uses remote KV and D1 bindings and is never deployed.
451
+
452
+ Prepare the new configuration without deploying it. Use the existing activity
453
+ D1 database as `CONNECTA_DB`, keeping its name and id; otherwise create one
454
+ with `wrangler d1 create connecta`. Remove `kv_namespaces` and copied adapters.
455
+ Configuration lives in `src/connecta.config.ts` and
456
+ `src/index.ts` is the entry point. Set the plain var `CONNECTA_ACTIVITY = "on"`
457
+ so preserved activity remains visible. Bind only `CONNECTA_DB` and `LOADER` for storage/execution;
458
+ keep existing auth vars and the credential secret.
459
+ Fill in the copy config's namespace id and the same D1 database name/id,
460
+ then `wrangler login` and check the prepared deployment with a dry run.
461
+
462
+ 1. Back up KV with a bulk export including values and expiration metadata.
463
+ Export D1 with `wrangler d1 export <db> --remote --output before-cutover.sql`.
464
+ Store backups securely and preserve the exact credential encryption key,
465
+ including One&Many's `CONNECTA_CREDENTIALS_KEY`.
466
+ 2. Block traffic using maintenance mode or turn the route off. Stop cron,
467
+ queue consumers, scheduled jobs, and every other KV/D1 writer. Drain all
468
+ in-flight requests, including OAuth callbacks and token refreshes.
469
+ 3. Wait at least **60 seconds after the last write**, or the longest configured
470
+ KV `cacheTtl`, whichever is greater. KV propagation can take longer, so this
471
+ is a minimum, not proof of consistency. See
472
+ [KV consistency](https://developers.cloudflare.com/kv/concepts/how-kv-works/).
473
+ Confirm a stable source with two consecutive listing/hash passes that match;
474
+ the script performs this check. If they differ, stay in maintenance, wait
475
+ longer, and repeat. Expiring entries may also require another pass.
476
+ 4. Copy **before switching traffic** with
477
+ `node scripts/copy-kv-to-d1.mjs --maintenance`. Resolve all `invalid` and
478
+ `conflicts` counts under maintenance; either makes the script exit non-zero.
479
+ An oversized entry requires a deliberate per-family remediation, not a
480
+ retry of the same value. Overwrite only families explicitly reviewed as
481
+ stale in D1, for example `--maintenance --overwrite-family catalog`.
482
+ Token/OAuth/vault families require the operator to confirm D1 rows are stale
483
+ and also pass `--confirm-stale-d1`; never infer this from a conflict alone.
484
+ There is no blanket `--overwrite`. Then run
485
+ `node scripts/copy-kv-to-d1.mjs --maintenance --verify`. It compares SHA-256
486
+ hashes of exact values and absolute expiries for live source keys, reports
487
+ `verified` / `mismatches` counts by family only, and exits non-zero on any
488
+ invalid entry or mismatch. Keep traffic and writers stopped until these
489
+ counts are resolved. A verification pass does not rewrite copied entries.
490
+ 5. Deploy the D1 configuration with `wrangler deploy` while maintenance remains.
491
+ Run `connecta doctor` using Access service credentials plus a stored `cta_`
492
+ bearer through `CONNECTA_TOKEN`. Verify an existing
493
+ OAuth connector without re-consent, an existing `cta_` token, a connector
494
+ using a vault credential, and prior activity in the Activity view. Run
495
+ these controlled checks with other traffic and background writers blocked.
496
+ 6. Seal the source with
497
+ `node scripts/copy-kv-to-d1.mjs --maintenance --mark-live`, then reopen
498
+ traffic and background writers. The permanent D1 marker refuses later
499
+ copies. Never rerun after traffic resumes. `--i-know-this-is-stale` is only
500
+ an emergency override after stopping writers again and assessing stale KV;
501
+ it does not make stale tokens or credentials safe to restore.
502
+ Retain backups before deleting the KV namespace. After a clean observation
503
+ period, remove the copy script/config and delete KV with
504
+ `wrangler kv namespace delete --namespace-id <id>`.
505
+
506
+ An in-Worker copy can call
507
+ `copyKvToD1(env.CONNECTA_KV, env.CONNECTA_DB, { source: namespaceId, cursor })`
508
+ from a guarded one-off route under the same maintenance procedure. Each call
509
+ reads at most `maxKeys` keys, default 500. For near-limit rows use 200 or fewer
510
+ so additional byte-bounded D1 batches fit the invocation's query budget.
511
+ Loop until `done`, passing each returned cursor. Tokens keep KV's raw cursor
512
+ in D1 for seven days, are bound to the source id, and are atomically consumed.
513
+ Unknown, spent, or foreign-source tokens are refused without echoing them.
514
+ On failure, use the error's replacement token or restart under maintenance.
515
+ Call `markKvToD1Live(db, namespaceId)` before reopening traffic, and remove the
516
+ route and KV binding. The source stability check is the operator's duty when
517
+ using the API directly. Remote bindings remain unverified until deploy day.
374
518
 
375
- ## Artifacts (optional)
519
+ ## Activity history (optional)
376
520
 
377
- `artifacts()` from `@zackbart/connecta/artifacts` adds the built-in `artifacts`
378
- connector: team pages whose data lives in versioned JSON documents. Every write
379
- commits by compare-and-set, so the store needs the D1 storage above — Workers
380
- KV is refused at construction. Artifacts share the `connecta_kv` table and need
381
- no schema of their own. Bodies (page sources and documents) can stay in D1 rows,
382
- which the default limits keep well under D1's row size, or go to R2 with
383
- `src/r2-artifact-blobs.ts`:
521
+ `d1ActivityStore(env.CONNECTA_DB)` from `@zackbart/connecta/d1` is a complete
522
+ `ActivityStore` in the same database — keyset paging on
523
+ `(occurred_at_ms, id)`, its table created on first use. `src/connecta.config.ts`
524
+ switches it on only when the `CONNECTA_ACTIVITY` var is `"on"`, because
525
+ retention is the deployment's decision:
384
526
 
385
527
  ```ts
386
- import { artifacts, kvArtifactStore } from "@zackbart/connecta/artifacts";
387
- import { r2ArtifactBlobs } from "./r2-artifact-blobs.js";
388
-
389
- createConnecta({
390
- // …
391
- publicUrl: env.PUBLIC_URL, // required: artifact links are shared
392
- artifacts: artifacts({
393
- store: kvArtifactStore(d1Storage(env.STORAGE_DB), {
394
- blobs: r2ArtifactBlobs(env.ARTIFACTS_BUCKET), // optional
395
- }),
396
- }),
397
- });
528
+ activity: env.CONNECTA_ACTIVITY === "on"
529
+ ? activityHistory({
530
+ store: d1ActivityStore(env.CONNECTA_DB, { retentionDays: 90 }),
531
+ deploymentId: "production",
532
+ })
533
+ : undefined,
398
534
  ```
399
535
 
400
- For R2, create the bucket (`wrangler r2 bucket create connecta-artifacts`) and
401
- add `"r2_buckets": [{ "binding": "ARTIFACTS_BUCKET", "bucket_name":
402
- "connecta-artifacts" }]` to `wrangler.jsonc` and `ARTIFACTS_BUCKET: R2Bucket`
403
- to `Env`. Nothing ever deletes a body: versions are immutable, a rollback
404
- points back at an old one, and a body written by a write that lost a conflict
405
- stays stored unreferenced.
406
-
407
- To refresh pages, keep the module in a variable, pass it to `createConnecta`,
408
- and assign it to `scheduledArtifacts.module` as shown in `src/index.ts`.
409
- Uncomment the hourly cron in `wrangler.jsonc`. An hourly tick starts at most
410
- 10 due pages; each page's `manual`, `daily`, or `weekly` schedule lives in its
411
- versioned refresh configuration. Refresh programs use only shared connectors'
412
- explicitly read-only tools within the program owner's current grants. Revoking
413
- refresh or pool access stops future runs. D1's compare-and-set prevents two cron invocations
414
- from claiming the same page. Failed runs keep the last good data and show a
415
- stale banner without exposing downstream error text to readers.
416
-
417
- ## Activity history (optional)
418
-
419
- `src/d1-activity.ts` is a complete `ActivityStore` over D1 — keyset paging on
420
- `(occurred_at_ms, id)` plus a batched retention pass — but the wiring in
421
- `src/index.ts` is **commented out**, so the example deploys without a database.
422
- To enable it:
423
-
424
- 1. Create the database and uncomment the `d1_databases` binding in
425
- `wrangler.jsonc`, pasting in the id it prints:
426
-
427
- ```sh
428
- wrangler d1 create connecta-activity
429
- ```
430
-
431
- ```jsonc
432
- "d1_databases": [
433
- { "binding": "ACTIVITY_DB", "database_name": "connecta-activity", "database_id": "…" }
434
- ]
435
- ```
436
-
437
- 2. Apply the schema (keep it in a deployment-owned `migrations/` directory):
438
-
439
- ```sql
440
- CREATE TABLE IF NOT EXISTS tool_call_activity (
441
- id TEXT PRIMARY KEY,
442
- occurred_at_ms INTEGER NOT NULL,
443
- request_id TEXT NOT NULL,
444
- actor_kind TEXT NOT NULL,
445
- actor_id TEXT,
446
- actor_namespace TEXT,
447
- connector_id TEXT NOT NULL,
448
- tool_name TEXT NOT NULL,
449
- source TEXT NOT NULL,
450
- outcome TEXT NOT NULL,
451
- duration_ms INTEGER NOT NULL,
452
- attempts INTEGER NOT NULL,
453
- error_code TEXT,
454
- friction TEXT,
455
- approval TEXT,
456
- server_name TEXT NOT NULL,
457
- server_version TEXT NOT NULL,
458
- deployment_id TEXT
459
- );
460
-
461
- CREATE INDEX IF NOT EXISTS tool_call_activity_recent
462
- ON tool_call_activity (occurred_at_ms DESC, id DESC);
463
- ```
464
-
465
- **Already have this table?** `actor_namespace`, `friction`, and `approval`
466
- were added after the original example, and `CREATE TABLE IF NOT EXISTS`
467
- will not add them to a table that already exists. Add them as migrations:
468
-
469
- ```sql
470
- ALTER TABLE tool_call_activity ADD COLUMN actor_namespace TEXT;
471
- ALTER TABLE tool_call_activity ADD COLUMN friction TEXT;
472
- ALTER TABLE tool_call_activity ADD COLUMN approval TEXT;
473
- ```
474
-
475
- `approval` is history: it is set only on an `approved` row written before
476
- 0.28.0 removed program pauses — the scope a resumed program's approval
477
- covered, `call` or `tool` — and is null on every row since. Keep the
478
- column; the Activity tab still renders those older rows.
479
-
480
- `friction` is stored rather than derived because one of its classes belongs
481
- to a call that *succeeded*: a result too large to return inline is friction
482
- for the agent and carries no error code. Rows written before the column keep
483
- working — the mapping module derives their friction from `error_code` — and
484
- `error_code IS NOT NULL` remains an honest count of failures.
485
-
486
- Do this **before** deploying the updated `d1-activity.ts`: its `INSERT`
487
- names the column, so against an un-migrated table every write fails with
488
- `no such column`. Activity writes are best-effort by design — connecta logs
489
- the failure and returns the tool result unharmed — so the symptom is not an
490
- error your agent sees, it is an activity log that quietly stops recording.
491
-
492
- 3. In `src/index.ts`, enable the `activityHistory` and `d1ActivityStore` imports,
493
- the `ACTIVITY_DB` field on `Env`, and the `activity` option:
494
-
495
- ```ts
496
- import { activityHistory } from "@zackbart/connecta/activity";
497
- import { d1ActivityStore } from "./d1-activity.js";
498
-
499
- createConnecta({
500
- // …
501
- activity: activityHistory({
502
- store: d1ActivityStore(env.ACTIVITY_DB),
503
- deploymentId: "production",
504
- }),
505
- });
506
- ```
507
-
508
- Events carry no arguments, results, generated code, or raw error messages.
509
- The Worker entrypoint already forwards `ctx` to `connecta.fetch`, which lets
510
- async activity writes settle on `waitUntil`.
511
-
512
- For retention, add a
513
- [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/)
514
- (`triggers.crons` in `wrangler.jsonc` plus a `scheduled` handler — this example
515
- no longer ships one) and call `pruneActivity(env.ACTIVITY_DB, retentionDays)`
516
- from it.
536
+ Each write prunes a bounded batch of rows older than `retentionDays` (90 by
537
+ default), so no Cron Trigger is needed; change the number there. Events carry no arguments, results, generated code,
538
+ or raw error messages. The Worker entrypoint already forwards `ctx` to
539
+ `connecta.fetch`, which lets async activity writes settle on `waitUntil`.
540
+
541
+ `friction` is stored rather than derived because one of its classes belongs to
542
+ a call that _succeeded_: a result too large to return inline is friction for
543
+ the agent and carries no error code. Rows written before the column derive
544
+ their friction from `error_code`, and `error_code IS NOT NULL` remains an
545
+ honest count of failures. `approval` is history, set only on `approved` rows
546
+ written before 0.28.0 removed program pauses.
547
+
548
+ Activity uses `d1ActivityStore` and the shared SQL row mapping. On first access,
549
+ it adds nullable classification, result byte count, event kind, catalog-change
550
+ counts, pool and actor-basis columns. Existing rows retain unknown facts. Personal
551
+ rows require the checked `principal` actor basis before their ownership is trusted.
552
+ Request IDs already
553
+ exist in the table. Catalog-change events contain checked counts only; tool-call
554
+ results and payloads are never stored.