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