@zackbart/connecta 0.28.0 → 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.
- package/CHANGELOG.md +961 -5
- package/README.md +62 -39
- package/bin/connecta.mjs +110 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1632 -1509
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +526 -0
- package/dist/auth-elicitation.js +283 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +368 -224
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +631 -0
- package/dist/config.js +503 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +220 -0
- package/dist/connectors/api-connector.js +214 -0
- package/dist/connectors/api.d.ts +8 -71
- package/dist/connectors/api.js +13 -99
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +108 -25
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1780 -351
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +119 -0
- package/dist/downstream-input.js +323 -0
- package/dist/errors.d.ts +97 -20
- package/dist/errors.js +158 -80
- package/dist/execute.js +453 -305
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +24 -9
- package/dist/executors/quickjs-child.js +3 -4
- package/dist/executors/quickjs-runtime.js +147 -59
- package/dist/executors/quickjs.js +29 -35
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +188 -0
- package/dist/guest-types.d.ts +176 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +25 -257
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +230 -73
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +76 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +285 -209
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +117 -0
- package/dist/oauth-partition.js +17 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +55 -18
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/basecamp/index.d.ts +43 -0
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/breeze/index.d.ts +34 -0
- package/dist/providers/breeze/index.js +1446 -0
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/ccb/index.d.ts +103 -0
- package/dist/providers/ccb/index.js +1725 -0
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/overflow/index.d.ts +60 -0
- package/dist/providers/overflow/index.js +1535 -0
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/planning-center/index.d.ts +59 -0
- package/dist/providers/planning-center/index.js +2199 -0
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/tithely/index.d.ts +49 -0
- package/dist/providers/tithely/index.js +1003 -0
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +108 -171
- package/dist/registry.js +513 -925
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +100 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +356 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +136 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +52 -0
- package/dist/sent-secrets.js +474 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +406 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +166 -0
- package/dist/storage/keys.js +428 -0
- package/dist/storage/memory.js +7 -0
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +534 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +320 -54
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +137 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +434 -0
- package/documentation/architecture.md +665 -230
- package/documentation/auth.md +962 -458
- package/documentation/code-mode.md +501 -312
- package/documentation/meta-tools.md +449 -200
- package/documentation/operator-tests.md +93 -0
- package/documentation/provider-migration-0.29.md +60 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +286 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +125 -35
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +133 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- 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
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
16
|
-
|
|
|
17
|
-
| `src/index.ts`
|
|
18
|
-
| `src/
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
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
|
-
`
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
|
83
|
-
Access service
|
|
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
|
|
119
|
-
and
|
|
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
|
|
139
|
-
|
|
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 {
|
|
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
|
-
|
|
172
|
-
`
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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.
|
|
186
|
-
|
|
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
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
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
|
|
242
|
-
|
|
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/
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
289
|
-
service
|
|
290
|
-
|
|
291
|
-
|
|
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.
|
|
294
|
-
from `@zackbart/connecta/activity
|
|
295
|
-
|
|
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
|
|
331
|
+
Verify MCP health and the exact six tools with:
|
|
300
332
|
|
|
301
333
|
```sh
|
|
302
|
-
CF_ACCESS_CLIENT_ID
|
|
303
|
-
|
|
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/
|
|
324
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
`
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
##
|
|
512
|
+
## Activity history (optional)
|
|
376
513
|
|
|
377
|
-
`
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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.
|