@zackbart/connecta 0.37.1 → 0.39.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 (265) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +5 -4
  3. package/bin/connecta.mjs +1 -39
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.js +2 -4
  6. package/dist/agent-identity.js +1 -1
  7. package/dist/agent-routes.js +231 -0
  8. package/dist/auth/clerk-sdk/generated.js +2 -1
  9. package/dist/auth/downstream-oauth/consent.js +519 -0
  10. package/dist/auth/downstream-oauth/grant-store.js +439 -0
  11. package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
  12. package/dist/auth/downstream-oauth/refresh-records.js +447 -0
  13. package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
  14. package/dist/auth/downstream-oauth.js +66 -2516
  15. package/dist/auth/downstream-revocation.js +1 -1
  16. package/dist/auth/native.js +27 -0
  17. package/dist/auth/oauth-grant.js +24 -0
  18. package/dist/auth/oauth-token-response.js +1 -1
  19. package/dist/auth/static-oauth.js +153 -131
  20. package/dist/auth/vercel-agent.js +2 -1
  21. package/dist/auth-elicitation.js +9 -3
  22. package/dist/authorization-handoff.js +11 -5
  23. package/dist/branding.d.ts +1 -15
  24. package/dist/branding.js +2 -2
  25. package/dist/bytes.js +42 -0
  26. package/dist/call-admission-context.js +2 -5
  27. package/dist/call-recovery.js +0 -3
  28. package/dist/catalog-cache.js +130 -43
  29. package/dist/catalog-fingerprint.js +2 -18
  30. package/dist/catalog-policy.js +18 -0
  31. package/dist/catalog-service.js +285 -48
  32. package/dist/catalog.js +18 -2
  33. package/dist/config-defaults.d.ts +1 -0
  34. package/dist/config-defaults.js +1 -0
  35. package/dist/config-schema.d.ts +1 -1
  36. package/dist/config-schema.js +6 -2
  37. package/dist/config.d.ts +57 -25
  38. package/dist/config.js +30 -77
  39. package/dist/connector-caller.d.ts +1 -1
  40. package/dist/connector-health.js +59 -0
  41. package/dist/connector-status.js +117 -0
  42. package/dist/connectors/api-connector.d.ts +7 -2
  43. package/dist/connectors/api-connector.js +11 -7
  44. package/dist/connectors/guarded-fetch.d.ts +0 -6
  45. package/dist/connectors/guarded-fetch.js +1 -11
  46. package/dist/connectors/negotiation-cache.js +22 -4
  47. package/dist/connectors/remote-mcp/call.js +164 -0
  48. package/dist/connectors/remote-mcp/catalog.js +278 -0
  49. package/dist/connectors/remote-mcp/config.js +263 -0
  50. package/dist/connectors/remote-mcp/connection.js +671 -0
  51. package/dist/connectors/remote-mcp/errors.js +488 -0
  52. package/dist/connectors/remote-mcp/oauth.js +469 -0
  53. package/dist/connectors/remote-mcp/resources.js +473 -0
  54. package/dist/connectors/remote-mcp/runtime.js +1 -0
  55. package/dist/connectors/remote-mcp/transport.js +544 -0
  56. package/dist/connectors/remote-mcp.js +61 -2898
  57. package/dist/connectors/resource-uri.js +0 -4
  58. package/dist/credential-contract.d.ts +23 -0
  59. package/dist/credential-reads.js +17 -0
  60. package/dist/credential-verification.js +53 -0
  61. package/dist/credentials.d.ts +7 -0
  62. package/dist/credentials.js +60 -25
  63. package/dist/d1.d.ts +0 -89
  64. package/dist/d1.js +0 -34
  65. package/dist/describe-config.d.ts +12 -2
  66. package/dist/describe-config.js +2 -0
  67. package/dist/discovery-health.js +199 -0
  68. package/dist/downstream-input.js +4 -2
  69. package/dist/errors.d.ts +55 -17
  70. package/dist/errors.js +95 -41
  71. package/dist/execute.js +172 -125
  72. package/dist/executor-result.js +5 -2
  73. package/dist/executors/quickjs-runtime.js +2 -25
  74. package/dist/executors/quickjs.js +1 -1
  75. package/dist/guest-functions.js +83 -0
  76. package/dist/guest-runtime.js +63 -20
  77. package/dist/guest-types.d.ts +24 -10
  78. package/dist/http/argument-problems.js +35 -0
  79. package/dist/http/client.js +1 -1
  80. package/dist/http/errors.js +7 -6
  81. package/dist/http/fields.js +192 -13
  82. package/dist/http/hatch-response.js +73 -0
  83. package/dist/http/hatch.js +88 -23
  84. package/dist/http/json-schema.js +1 -1
  85. package/dist/http/node-transport.js +0 -1
  86. package/dist/http/oauth.js +4 -3
  87. package/dist/http/paging.js +6 -9
  88. package/dist/http/provider.js +33 -5
  89. package/dist/http/safety.js +9 -1
  90. package/dist/http/services.js +1 -8
  91. package/dist/http/tools.js +45 -30
  92. package/dist/http/vendor.js +7 -7
  93. package/dist/identity.js +2 -6
  94. package/dist/index.d.ts +1 -1
  95. package/dist/index.js +9 -2
  96. package/dist/invocation-auth.js +2 -1
  97. package/dist/invocation.js +21 -6
  98. package/dist/membership.d.ts +13 -5
  99. package/dist/membership.js +34 -2
  100. package/dist/meta-tools.js +50 -35
  101. package/dist/native-recovery.js +4 -16
  102. package/dist/native-tools.js +6 -2
  103. package/dist/oauth-handoff.js +2 -2
  104. package/dist/oauth-sealing.js +10 -0
  105. package/dist/operation-vocabulary.js +136 -14
  106. package/dist/operator-record.d.ts +2 -2
  107. package/dist/operator-record.js +2 -49
  108. package/dist/operator-ui/contract.d.ts +12 -2
  109. package/dist/operator-ui/generated.js +3 -3
  110. package/dist/operator-ui/model.d.ts +22 -1
  111. package/dist/program-writes.js +3 -8
  112. package/dist/provider-call.js +0 -7
  113. package/dist/provider.d.ts +1 -1
  114. package/dist/providers/_shared/google/kit.js +1 -6
  115. package/dist/providers/_shared/google/service-account.js +1 -3
  116. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  117. package/dist/providers/_shared/guide.js +3 -0
  118. package/dist/providers/_shared/json.js +12 -0
  119. package/dist/providers/_shared/rest/compact-index.js +1 -1
  120. package/dist/providers/_shared/rest/kit.js +1 -3
  121. package/dist/providers/_shared/rest/operation-index.js +49 -53
  122. package/dist/providers/_shared/rest/value-safety.js +106 -28
  123. package/dist/providers/breeze/api.js +9 -8
  124. package/dist/providers/breeze/index.js +4 -2
  125. package/dist/providers/breeze/skill.generated.js +1 -1
  126. package/dist/providers/breeze/tools.js +7 -7
  127. package/dist/providers/ccb/api.js +3 -7
  128. package/dist/providers/ccb/index.js +7 -1
  129. package/dist/providers/ccb/skill.generated.js +1 -1
  130. package/dist/providers/ccb/tools.js +1 -6
  131. package/dist/providers/cloudflare/api.js +10 -3
  132. package/dist/providers/cloudflare/index.js +2 -1
  133. package/dist/providers/cloudflare/safety.js +3 -3
  134. package/dist/providers/cloudflare/skill.generated.js +1 -1
  135. package/dist/providers/docs/lib/schemas.js +24 -22
  136. package/dist/providers/docs/safety.js +2 -0
  137. package/dist/providers/docs/skill.generated.js +1 -1
  138. package/dist/providers/docs/tools.js +4 -14
  139. package/dist/providers/drive/api.js +1 -0
  140. package/dist/providers/drive/lib/contracts.js +8 -5
  141. package/dist/providers/drive/skill.generated.js +1 -1
  142. package/dist/providers/drive/tools.js +23 -41
  143. package/dist/providers/forms/api.js +1 -0
  144. package/dist/providers/forms/skill.generated.js +1 -1
  145. package/dist/providers/forms/tools.js +11 -16
  146. package/dist/providers/github/api.js +1 -0
  147. package/dist/providers/github/lib/discovery.js +1 -1
  148. package/dist/providers/github/skill.generated.js +1 -1
  149. package/dist/providers/github/tools.js +61 -17
  150. package/dist/providers/gmail/lib/schemas.js +60 -50
  151. package/dist/providers/gmail/skill.generated.js +1 -1
  152. package/dist/providers/gmail/tools.js +12 -26
  153. package/dist/providers/granola/index.js +2 -0
  154. package/dist/providers/granola/skill.generated.js +1 -1
  155. package/dist/providers/overflow/api.js +8 -4
  156. package/dist/providers/overflow/index.js +2 -1
  157. package/dist/providers/overflow/skill.generated.js +1 -1
  158. package/dist/providers/overflow/tools.js +3 -7
  159. package/dist/providers/planning-center/api.js +5 -2
  160. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  161. package/dist/providers/planning-center/lib/json-api.js +1 -9
  162. package/dist/providers/planning-center/skill.generated.js +1 -1
  163. package/dist/providers/planning-center/tools.js +2 -1
  164. package/dist/providers/quickbooks/safety.js +80 -39
  165. package/dist/providers/quickbooks/skill.generated.js +1 -1
  166. package/dist/providers/sheets/api.js +1 -0
  167. package/dist/providers/sheets/skill.generated.js +1 -1
  168. package/dist/providers/sheets/tools.js +14 -23
  169. package/dist/providers/slides/skill.generated.js +1 -1
  170. package/dist/providers/slides/tools.js +19 -26
  171. package/dist/providers/stripe/index.js +2 -1
  172. package/dist/providers/stripe/safety.js +2 -2
  173. package/dist/providers/stripe/skill.generated.js +1 -1
  174. package/dist/providers/tithely/api.js +5 -8
  175. package/dist/providers/tithely/index.js +3 -2
  176. package/dist/providers/tithely/skill.generated.js +1 -1
  177. package/dist/providers/tithely/tools.js +7 -5
  178. package/dist/providers/vercel/api.js +2 -7
  179. package/dist/providers/vercel/lib/pin.js +1 -1
  180. package/dist/providers/vercel/safety.js +1 -1
  181. package/dist/providers/vercel/skill.generated.js +1 -1
  182. package/dist/providers/vercel/tools.js +6 -4
  183. package/dist/redaction-marker.js +2 -2
  184. package/dist/registry.d.ts +11 -5
  185. package/dist/registry.js +46 -20
  186. package/dist/request-principal.js +1 -1
  187. package/dist/request-state.js +2 -2
  188. package/dist/routes/access-tokens.js +1 -1
  189. package/dist/routes/activity-disclosure.js +1 -1
  190. package/dist/routes/connect.js +2 -2
  191. package/dist/routes/credentials.js +130 -61
  192. package/dist/routes/dispatch.js +5 -1
  193. package/dist/routes/mcp.js +34 -17
  194. package/dist/routes/oauth-management.js +1 -1
  195. package/dist/routes/operator.js +6 -5
  196. package/dist/routes/shared.d.ts +13 -8
  197. package/dist/routes/shared.js +15 -9
  198. package/dist/routes/ui-config.js +3 -3
  199. package/dist/routes/ui-data.js +126 -20
  200. package/dist/routes/ui.js +17 -1
  201. package/dist/runtime/call-admission.js +1 -1
  202. package/dist/runtime/call.js +8 -3
  203. package/dist/runtime/native-storage.js +9 -0
  204. package/dist/runtime/request-reads.js +182 -0
  205. package/dist/runtime/request-scope.js +16 -0
  206. package/dist/runtime/settlement.js +1 -1
  207. package/dist/runtime/shared-read.js +2 -15
  208. package/dist/runtime/storage.js +12 -0
  209. package/dist/sent-secrets.d.ts +1 -2
  210. package/dist/sent-secrets.js +3 -8
  211. package/dist/server.js +4 -4
  212. package/dist/skills.js +44 -54
  213. package/dist/sqlite.d.ts +0 -19
  214. package/dist/sqlite.js +2 -83
  215. package/dist/storage/keys.js +69 -128
  216. package/dist/storage/memory.js +8 -2
  217. package/dist/storage/sql-native.js +27 -101
  218. package/dist/storage/sql.d.ts +1 -24
  219. package/dist/storage/sql.js +13 -14
  220. package/dist/storage-digest.js +32 -0
  221. package/dist/timeout.js +8 -0
  222. package/dist/tool-error.js +4 -0
  223. package/dist/types.d.ts +33 -0
  224. package/dist/url-safety.js +0 -4
  225. package/dist/usage-guide.js +18 -37
  226. package/dist/validate.d.ts +3 -0
  227. package/dist/validate.js +220 -158
  228. package/dist/version.d.ts +1 -1
  229. package/dist/version.js +1 -1
  230. package/dist/worker.js +1 -1
  231. package/dist/write-outcome.js +0 -3
  232. package/documentation/README.md +19 -55
  233. package/documentation/architecture.md +436 -515
  234. package/documentation/auth.md +229 -88
  235. package/documentation/code-mode.md +201 -94
  236. package/documentation/deploying.md +25 -43
  237. package/documentation/integrating.md +18 -19
  238. package/documentation/meta-tools.md +225 -51
  239. package/documentation/migration-0.33.md +10 -6
  240. package/documentation/migration-0.34.md +5 -5
  241. package/documentation/migration-retired-layouts.md +74 -0
  242. package/documentation/operator-tests.md +20 -120
  243. package/documentation/operator-ui.md +108 -16
  244. package/documentation/pool-membership-upgrade.md +44 -0
  245. package/documentation/provider-auth-migration.md +4 -10
  246. package/documentation/provider-migration-0.29.md +4 -15
  247. package/documentation/providers.md +105 -0
  248. package/examples/worker/AGENTS.md +14 -16
  249. package/examples/worker/README.md +24 -118
  250. package/examples/worker/src/connecta.config.ts +1 -0
  251. package/guides/connecting-agents.md +9 -31
  252. package/guides/how-it-works.md +6 -4
  253. package/guides/safety-and-trust.md +4 -1
  254. package/package.json +19 -19
  255. package/templates/node/AGENTS.md +13 -12
  256. package/templates/node/README.md +9 -26
  257. package/templates/node/package.json +1 -1
  258. package/templates/node/src/connecta.config.ts +1 -0
  259. package/dist/auth/oauth-v2-migration.js +0 -135
  260. package/dist/provider-call-facts.d.ts +0 -115
  261. package/dist/provider-call.d.ts +0 -112
  262. package/dist/storage/d1-copy.js +0 -255
  263. package/dist/storage/sql-schema.d.ts +0 -6
  264. package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
  265. package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@clerk/backend": "^3.12.0",
21
- "@zackbart/connecta": "0.37.1",
21
+ "@zackbart/connecta": "0.39.0",
22
22
  "quickjs-emscripten": "0.32.0",
23
23
  "undici": "6.29.1"
24
24
  },
@@ -102,6 +102,7 @@ export default defineConfig((env: Env) => {
102
102
  // `npm run doctor` checks /agent/local-machine with its token. The starter
103
103
  // pool is the agent's base pool, its lowest tier (shared connectors, no
104
104
  // owner), named after the agent as the agent pool layout requires.
105
+ // Each pool lists only people or only agents; share tool lists across separate pools.
105
106
  pools: { "local-machine": { tools: ["time"], members: ["agent:local-machine"] } },
106
107
  // Machine clients are agents declared here by name, each with one cta_
107
108
  // token. Issue or rotate one locally with
@@ -1,135 +0,0 @@
1
- import { runEdge } from "../runtime/run.js";
2
- import { oauthV2Keys } from "../storage/keys.js";
3
- import { authAll, authCall, authFlow, authInvoke, authMap, authMethod, authProgram, authStorage, registerAuth, } from "./native.js";
4
- function plainObject(value) {
5
- return value !== null && typeof value === "object" && !Array.isArray(value);
6
- }
7
- function parse(raw) {
8
- try {
9
- return JSON.parse(raw);
10
- }
11
- catch {
12
- return undefined;
13
- }
14
- }
15
- /** The issuer a discovery record names, as the SDK derives it. */
16
- export function discoveryIssuer(state) {
17
- if (!plainObject(state) || typeof state.authorizationServerUrl !== "string")
18
- return undefined;
19
- const metadata = state.authorizationServerMetadata;
20
- const issuer = plainObject(metadata) && typeof metadata.issuer === "string" ? metadata.issuer : state.authorizationServerUrl;
21
- return issuer === "" ? undefined : issuer;
22
- }
23
- /**
24
- * Layout 2's grant in this namespace, or nothing when it holds none.
25
- *
26
- * Only issuer-stamped envelopes (`connectaOAuthVersion: 2`, written since
27
- * v0.9.0) of the active generation are read; anything older, and any value
28
- * the vault cannot open, is left behind, as layout 2's own flow entry would
29
- * have retired it. A grant is kept only when every credential carries a
30
- * stamp, the stamps agree, and they name the server the generation's
31
- * discovery names, if any; otherwise it migrates empty and needs consent.
32
- * One-shot consent state (pending URL, verifier, state) is not carried.
33
- */
34
- export function readV2Grant(storage, sealer) {
35
- return runEdge(authProgram(readV2GrantNative, [storage, sealer]));
36
- }
37
- const readV2GrantNative = /* @__PURE__ */ registerAuth(readV2Grant, function (storage, sealer) {
38
- return authFlow(function* () {
39
- const keys = (yield authCall(listV2Keys, [storage]));
40
- if (keys.length === 0)
41
- return undefined;
42
- const generation = (yield authStorage(storage, "get", [oauthV2Keys.generation])) ?? "legacy";
43
- if (generation.startsWith("disconnected:"))
44
- return { disconnected: true, body: {} };
45
- if (!generation.startsWith("v2:") && generation !== "legacy") {
46
- // An unfinished reset, or a numeric generation from before epochs.
47
- return { disconnected: false, body: {} };
48
- }
49
- const epoch = generation === "legacy" ? null : generation;
50
- const read = authMethod((field) => {
51
- return authFlow(function* () {
52
- const physicalKey = oauthV2Keys.value(field, epoch);
53
- const raw = (yield authStorage(storage, "get", [physicalKey]));
54
- if (raw === null)
55
- return undefined;
56
- let parsed = parse(raw);
57
- if (plainObject(parsed) && parsed.connectaOAuthSealed === 1 && typeof parsed.sealed === "string") {
58
- if (!sealer)
59
- return undefined;
60
- try {
61
- parsed = parse((yield authInvoke(sealer, "open", [physicalKey, parsed.sealed])));
62
- }
63
- catch {
64
- return undefined;
65
- }
66
- }
67
- if (plainObject(parsed) &&
68
- parsed.connectaOAuthVersion === 2 &&
69
- parsed.generation === generation &&
70
- (parsed.issuer === undefined || typeof parsed.issuer === "string") &&
71
- (parsed.binding === undefined || typeof parsed.binding === "string")) {
72
- return {
73
- value: parsed.value,
74
- ...(parsed.issuer !== undefined ? { issuer: parsed.issuer } : {}),
75
- ...(parsed.binding !== undefined ? { binding: parsed.binding } : {}),
76
- ...(parsed.carried === true ? { carried: true } : {}),
77
- };
78
- }
79
- // Layout 2 wrote discovery bare under the legacy generation.
80
- return field === oauthV2Keys.field.discovery && epoch === null && discoveryIssuer(parsed)
81
- ? { value: parsed }
82
- : undefined;
83
- });
84
- });
85
- const [client, tokens, discovery] = (yield authAll([
86
- authCall(read, [oauthV2Keys.field.client]),
87
- authCall(read, [oauthV2Keys.field.tokens]),
88
- authCall(read, [oauthV2Keys.field.discovery]),
89
- ], false));
90
- const named = discovery ? discoveryIssuer(discovery.value) : undefined;
91
- const credentials = [client, tokens].filter((value) => value !== undefined);
92
- const issuer = credentials[0]?.issuer ?? named;
93
- const consistent = credentials.every((value) => value.issuer !== undefined && value.issuer === issuer && plainObject(value.value)) &&
94
- (named === undefined || named === issuer);
95
- if (!consistent || issuer === undefined)
96
- return { disconnected: false, body: {} };
97
- return {
98
- disconnected: false,
99
- body: {
100
- issuer,
101
- ...(client
102
- ? {
103
- client: {
104
- value: client.value,
105
- ...(client.binding !== undefined ? { binding: client.binding } : {}),
106
- ...(client.carried ? { carried: true } : {}),
107
- },
108
- }
109
- : {}),
110
- ...(tokens ? { tokens: tokens.value } : {}),
111
- ...(discovery && named !== undefined ? { discovery: discovery.value } : {}),
112
- },
113
- };
114
- });
115
- });
116
- /** Only historical families are removed, never grants, flows, or leases. */
117
- function listV2Keys(storage) {
118
- return runEdge(authProgram(listV2KeysNative, [storage]));
119
- }
120
- const listV2KeysNative = /* @__PURE__ */ registerAuth(listV2Keys, function (storage) {
121
- return authFlow(function* () {
122
- return (yield authStorage(storage, "list", [oauthV2Keys.scan])).filter((key) => oauthV2Keys.family.prefixes.some((prefix) => key.startsWith(prefix)));
123
- });
124
- });
125
- /** Return true only when every historical key was deleted. */
126
- export function deleteV2Keys(storage) {
127
- return runEdge(authProgram(deleteV2KeysNative, [storage]));
128
- }
129
- const deleteV2KeysNative = /* @__PURE__ */ registerAuth(deleteV2Keys, function (storage) {
130
- return authFlow(function* () {
131
- const keys = (yield authCall(listV2Keys, [storage]));
132
- const results = (yield authMap(keys, (key) => authStorage(storage, "delete", [key]), true));
133
- return results.every((result) => result.status === "fulfilled");
134
- });
135
- });
@@ -1,115 +0,0 @@
1
- import { ConnectorCallError } from "./errors.js";
2
- import type { Connector } from "./types.js";
3
- export interface ProviderRequest {
4
- readonly dispatched: boolean;
5
- readonly status?: number;
6
- auxiliary: boolean;
7
- /** A kit auxiliary declared as a read lacked physical GET/HEAD proof at dispatch. */
8
- readonly blocksAuxiliaryProof?: true;
9
- provider: string;
10
- /** The HTTP method it was sent with, when its channel knows it. */
11
- readonly method?: string;
12
- /** Positive operation/tool evidence; an HTTP method alone proves no read. */
13
- classification?: "read" | "write" | undefined;
14
- /** Remote MCP's source class said write, recorded at physical dispatch. */
15
- readonly sourceWrite?: true;
16
- /** The genuine, legal idempotency key the vendor honors that it carried; nothing else. */
17
- key?: string;
18
- /**
19
- * Its answer was a 401 that the transport's reviewed `authBeforeExecution`
20
- * evidence covers: the vendor rejected the credential before it acted on
21
- * the request. Only that makes a 401 a refusal (`servedRefusal`).
22
- */
23
- authBeforeExecution?: true;
24
- /**
25
- * Its answer was a 401, and a renewal then replaced the credential it sent
26
- * with a different one ({@link renewedAfterUnauthorized}). The 401 no
27
- * longer needs the operator's recovery. The single `settleWrite` decider
28
- * still reports unknown, without the handoff (Q8).
29
- */
30
- readonly renewed?: true;
31
- }
32
- export type CallTool = Connector["callTool"];
33
- interface Boundary {
34
- /** The api() or remoteMcp() object whose own code enters the logical call. */
35
- boundary: object;
36
- own: CallTool;
37
- }
38
- export declare function originalCallIdentity(connector: object): object;
39
- export declare function boundaryFor(connector: object): Boundary | undefined;
40
- export declare function delegatorFor(connector: object): CallTool | undefined;
41
- /** A physical send owns its fields permanently; its own answer is recorded once. */
42
- export declare function recordRequestField(request: ProviderRequest, field: keyof ProviderRequest, value: unknown): void;
43
- export declare function recordProviderResponse(request: ProviderRequest, status: number): void;
44
- /**
45
- * Declare that every request the connector's handlers send goes through a
46
- * channel the call ledger records: a guarded transport, `ctx.fetch`, or
47
- * `ctx.oauth.fetch`. Only then does an empty ledger prove that a failed
48
- * write never left. Maintained providers declare it; an arbitrary `api()`
49
- * handler can reach the network some other way, so its ledger proves nothing.
50
- */
51
- export declare function tracksEveryRequest<T extends object>(connector: T): T;
52
- export declare function tracksRequests(connector: object): boolean;
53
- /**
54
- * Declare a maintained connector whose `callTool` hands each call to at most
55
- * one api() or remoteMcp() boundary and sends nothing else that could apply
56
- * it (GitHub). Only then may that boundary's verdict stand for the call. The
57
- * object and its function are recorded now, at construction: a later
58
- * assignment to the property, or a copy of the object, declares nothing.
59
- */
60
- export declare function delegatesEachCall<T extends Connector>(connector: T): T;
61
- /**
62
- * Record a just-built api() or remoteMcp() object as a boundary, with its
63
- * published `callTool`. Its own code then names the same object, from its
64
- * closure, in {@link enterProviderBoundary}.
65
- */
66
- export declare function providerBoundary(connector: Connector): void;
67
- /**
68
- * `alias` serves `base` itself (`defineProvider`'s inheriting stamp), so it
69
- * takes `base`'s recorded identities. Only construction code calls this.
70
- */
71
- export declare function aliasCallIdentity(alias: object, base: object): void;
72
- /**
73
- * Whether a served HTTP status says the request was refused, not applied: a
74
- * 4xx other than 408. A 408 is a timeout, whose request may have been acted
75
- * on; a 2xx or 5xx never proves a write unapplied. A 401 passes here, and
76
- * `servedRefusal` still asks for the vendor's reviewed evidence.
77
- */
78
- export declare function refusedStatus(status: number | undefined): boolean;
79
- /**
80
- * Refusal provenance, recorded where the answer is observed: the request was
81
- * served a refusing status (`refusedStatus`) and the mapper failed on it. An
82
- * error code alone is never this evidence.
83
- */
84
- export declare function vendorRefusal<T>(error: T, request: ProviderRequest): T;
85
- /** An authorization verdict, the kind that keeps the `authorize_connector` handoff. */
86
- export declare function authorizationFailure(error: unknown): error is ConnectorCallError;
87
- /**
88
- * The authorization verdict `request`'s own 401 answer was mapped to, as
89
- * {@link vendorRefusal} recorded it. Undefined when nothing mapped that
90
- * answer, such as a body past the ceiling.
91
- */
92
- export declare function unauthorizedAnswer(request: ProviderRequest): ConnectorCallError | undefined;
93
- /**
94
- * A renewal answered `request`'s own 401 (a refresh, a login, a fresh token
95
- * minted) with `fresh`, a credential other than `sent`, the one the request
96
- * carried: that 401 no longer needs the operator's recovery (Q8). It names
97
- * the one request it answered and never marks another. A renewal that hands
98
- * back the credential the request already sent (an earlier recovery reused)
99
- * renewed nothing.
100
- */
101
- export declare function renewedAfterUnauthorized(request: ProviderRequest | undefined, sent: string, fresh: string): void;
102
- export declare function refusalRequest(error: unknown): ProviderRequest | undefined;
103
- export declare function knownProviderFailure<T>(error: T): T;
104
- export declare function isKnownProviderFailure(error: unknown): boolean;
105
- /** Withdraw a verdict that does not hold for the call that reports it. */
106
- export declare function forgetProviderFailure(error: object): void;
107
- /** Preserve internal verdicts and resend advice through redaction and provider recovery wording. */
108
- export declare function carryProviderFailure<T>(from: unknown, to: T): T;
109
- /** Mark a verdict that already accounts for a write's 401 in its own words. */
110
- export declare function explainedUnauthorizedVerdict<T>(error: T): T;
111
- export declare function explainsUnauthorized(error: unknown): boolean;
112
- /** Mark a verdict the Q8 decision made, final for every step after it. */
113
- export declare function settledWrite<T>(error: T): T;
114
- export declare function isSettledWrite(error: unknown): boolean;
115
- export {};
@@ -1,112 +0,0 @@
1
- import { type ProviderRequest, type CallTool } from "./provider-call-facts.js";
2
- import type { ConnectorContext } from "./types.js";
3
- export interface ProviderCall {
4
- requests: ProviderRequest[];
5
- classification?: "read" | "write" | undefined;
6
- /** The handler settled: its outcome may be reported, so nothing more leaves. */
7
- closed: boolean;
8
- /** The calls of api() boundaries entered beneath this one's handler. */
9
- nested: ProviderCall[];
10
- /** The call whose handler entered this one; undefined for the outermost. */
11
- parent?: ProviderCall;
12
- }
13
- export interface LogicalCall {
14
- target: object;
15
- entered: CallTool;
16
- boundaries: object[];
17
- /** Every resource request dispatched under it, once per dispatch, from any ledger. */
18
- sent: ProviderRequest[];
19
- /** Auxiliary GET/HEAD dispatches without method overrides, apart from resource writes and auth POSTs. */
20
- auxiliaryReads: ProviderRequest[];
21
- /** A kit auxiliary declared as a read dispatched without physical read proof. */
22
- nonReadAuxiliaryDispatched: boolean;
23
- }
24
- export declare function providerCallContext(ctx: ConnectorContext, classification?: "read" | "write"): ConnectorContext;
25
- /** Whether `ctx`'s call was entered by another api() boundary's handler. */
26
- export declare function nestedProviderCall(ctx: ConnectorContext): boolean;
27
- export declare function providerRequests(ctx: ConnectorContext): readonly ProviderRequest[] | undefined;
28
- /**
29
- * Every request the ledgers of api() boundaries nested beneath `ctx`'s call
30
- * recorded, at any depth: an inner call's 401 still decides the outer's
31
- * write (Q8), whatever the outer handler made of the inner's failure.
32
- */
33
- export declare function nestedProviderRequests(ctx: ConnectorContext): ProviderRequest[];
34
- /**
35
- * End the call's ledger before its outcome is decided. A request still in
36
- * flight already counts as dispatched; one that has not left yet (a sibling
37
- * still reading its credential, say) is refused by {@link dispatchProviderRequest}
38
- * and never leaves after the failure is reported.
39
- */
40
- export declare function closeProviderCall(ctx: ConnectorContext): void;
41
- /**
42
- * Refuse every request after its request scope ends, before credential reads
43
- * and again immediately before dispatch. Resource requests also require an
44
- * open call. Auxiliary requests (token mints, lookups) may outlive that call,
45
- * but never their request.
46
- */
47
- export declare function assertProviderCallOpen(ctx: ConnectorContext, provider: string, auxiliary: boolean): void;
48
- /** Record that a request leaves now; synchronous with the send that follows it. */
49
- export declare function dispatchProviderRequest(ctx: ConnectorContext | undefined, request: ProviderRequest, headers?: HeadersInit, method?: string | undefined): void;
50
- export declare function recordingFetch<T extends object>(send: T): T;
51
- export declare function isRecordingFetch(send: object | undefined): boolean;
52
- export declare function handoffProviderRequest(init: RequestInit, request: ProviderRequest): void;
53
- export declare function takeProviderHandoff(init: RequestInit): ProviderRequest | undefined;
54
- export declare function bindProviderResponse(response: object, request: ProviderRequest, owner: ProviderRequest | undefined): void;
55
- export declare function responseProviderRequest(response: object, owner: ProviderRequest): ProviderRequest | undefined;
56
- export declare function providerRequest(ctx: ConnectorContext, auxiliary: boolean, provider: string, sent?: {
57
- method?: string;
58
- key?: string;
59
- classification?: "read" | "write";
60
- }): ProviderRequest;
61
- /**
62
- * The invocation opens one logical call on the context it hands a connector,
63
- * naming the exact object and `callTool` function it is about to invoke.
64
- */
65
- export declare function openLogicalCall(ctx: ConnectorContext, target: object, entered: CallTool): LogicalCall;
66
- /**
67
- * An api() or remoteMcp() boundary was entered under `ctx`'s logical call.
68
- * `boundary` is the object it was built as, from its own closure, never `this`.
69
- */
70
- export declare function enterProviderBoundary(ctx: ConnectorContext, boundary: object): void;
71
- /**
72
- * Whether a failure's known mark holds for its whole logical call (INV-9).
73
- * A boundary marks a write known only when every request it dispatched is
74
- * proven unapplied, so the mark stands when that boundary owned the call
75
- * ({@link ownsLogicalCall}) and at most one boundary was entered. A wrapper
76
- * that composed several calls, or could send around them, never inherits an
77
- * inner verdict.
78
- */
79
- export declare function knownCallFailure(call: LogicalCall | undefined, error: unknown): boolean;
80
- /**
81
- * Positive proof that a complete tracked call dispatched only auxiliaries,
82
- * including a recorded resource read (positive GET/HEAD evidence). An
83
- * authentication POST alone is not evidence of that call's resource work:
84
- * token mints can be shared with callers whose ledgers did not send them.
85
- * An empty resource ledger is not this proof: even a declared api() entry
86
- * can report a timeout and recovery facts without recording any dispatch.
87
- * Keep those calls conservative rather than inferring an auxiliary failure
88
- * from the absence of a resource request. Wrappers and untracked boundaries
89
- * cannot borrow an inner call's observations.
90
- */
91
- export declare function onlyAuxiliaryRequests(call: LogicalCall | undefined): boolean;
92
- /**
93
- * Whether reusing `key` with the exact original arguments cannot repeat any
94
- * write of `ctx`'s logical call, the one basis for key-reuse advice (INV-9).
95
- * It holds only when the call entered exactly one boundary, which owned it
96
- * and tracks every request, and sent exactly one write, carrying `key`. A
97
- * request counts as a read only with positive classification and a recorded
98
- * GET or HEAD ({@link readRequest}). An inner request's key never speaks for a composite,
99
- * a wrapper, a second write, or an earlier unrelated request.
100
- */
101
- export declare function keyProtectsCall(call: LogicalCall | undefined, key: string | undefined): boolean;
102
- /** Positive read evidence, with GET/HEAD as #902's additional conservative bound. */
103
- export declare function readRequest(request: Pick<ProviderRequest, "classification" | "method">, headers?: HeadersInit): boolean;
104
- export declare function providerFetch(ctx: ConnectorContext, provider: string, auxiliary: boolean, send: typeof fetch): typeof fetch;
105
- /** Whether an invocation opened the logical call `ctx` belongs to; it then settles Q8 itself. */
106
- export declare function inLogicalCall(ctx: object): boolean;
107
- /**
108
- * Every resource request dispatched under `ctx`'s logical call, from any
109
- * ledger, nested ones included; undefined outside one.
110
- */
111
- export declare function logicalCallRequests(ctx: object | undefined): readonly ProviderRequest[] | undefined;
112
- export { recordProviderResponse, tracksEveryRequest, tracksRequests, delegatesEachCall, providerBoundary, refusedStatus, vendorRefusal, authorizationFailure, unauthorizedAnswer, renewedAfterUnauthorized, refusalRequest, knownProviderFailure, isKnownProviderFailure, forgetProviderFailure, explainedUnauthorizedVerdict, explainsUnauthorized, settledWrite, isSettledWrite, type ProviderRequest, } from "./provider-call-facts.js";
@@ -1,255 +0,0 @@
1
- // Native copy orchestration. Only Workers KV and Web Crypto are Promise leaves.
2
- import { Effect } from "effect";
3
- import { errorLabel } from "../operator-record.js";
4
- import { KvToD1CopyError } from "../d1.js";
5
- import { familyOfKey, KEY_FAMILIES, KV_COPY_CURSOR_TTL_SECONDS, kvCopyKeys, kvCutoverKeys } from "./keys.js";
6
- import { copyIntoSqlEffect, nativeSqlStorage } from "./sql-native.js";
7
- import { nativeSqlDriver } from "./sql-driver.js";
8
- import { d1Driver } from "./d1-driver.js";
9
- const leaf = (operation) => Effect.tryPromise({ try: operation, catch: (error) => error });
10
- // A native equivalent of a Promise try/catch. Synchronous parser/validation
11
- // throws keep their original identity too; interruption is never recovered.
12
- const attempt = (effect) => Effect.result(effect.pipe(Effect.catchDefect((error) => Effect.fail(error))));
13
- /** D1 string and row limit, in UTF-8 bytes.
14
- * https://developers.cloudflare.com/d1/platform/limits/
15
- */
16
- const D1_MAX_ROW_BYTES = 2000000;
17
- /** Buffered entries, excluding the single value currently being read. */
18
- const COPY_BUFFER_BYTES = 4 * 1024 * 1024;
19
- const COPY_GROUP_KEYS = 50;
20
- const KV_LIST_LIMIT = 1000;
21
- const encoder = new TextEncoder();
22
- // SQLite record header, two TEXT serial types, and an eight-byte expiry.
23
- // Reserve the worst-case header rather than accepting a row at the boundary.
24
- const ROW_OVERHEAD_BYTES = 32;
25
- const emptyCounts = () => ({
26
- copied: 0,
27
- unchanged: 0,
28
- conflicts: 0,
29
- overwritten: 0,
30
- expired: 0,
31
- invalid: 0,
32
- verified: 0,
33
- mismatches: 0,
34
- });
35
- const OUTCOME_COUNT = {
36
- copied: "copied",
37
- unchanged: "unchanged",
38
- conflict: "conflicts",
39
- overwritten: "overwritten",
40
- };
41
- function errorClass(error) {
42
- const label = errorLabel(error);
43
- return label ? ` (${label})` : "";
44
- }
45
- const valueHash = (value) => Effect.gen(function* () {
46
- const hash = yield* leaf(() => crypto.subtle.digest("SHA-256", encoder.encode(value)));
47
- return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("");
48
- });
49
- /** Mark cutover before reopening traffic. The marker stays in D1 permanently. */
50
- export function markKvToD1LiveEffect(db, source) {
51
- return Effect.gen(function* () {
52
- if (typeof source !== "string" || !source || source.includes("\0")) {
53
- throw new TypeError("copyKvToD1 source must be a namespace id");
54
- }
55
- const cutover = yield* attempt(nativeSqlStorage(nativeSqlDriver(d1Driver(db))).set(kvCutoverKeys.source(source), "live"));
56
- if (cutover._tag === "Failure") {
57
- const error = cutover.failure;
58
- throw new KvToD1CopyError(`Workers KV to D1 copy stopped: recording cutover failed${errorClass(error)}`, undefined);
59
- }
60
- });
61
- }
62
- /**
63
- * Copy live KV entries verbatim while all writers and traffic are stopped.
64
- * A live D1 row is kept unless its family is explicitly allowed to overwrite.
65
- * `verify` reads hashes and absolute expiries without changing copied entries.
66
- * Results contain counts only. Resume tokens are source-bound atomic claims.
67
- */
68
- export function copyKvToD1Effect(kv, db, options) {
69
- return Effect.gen(function* () {
70
- if (!options?.source || typeof options.source !== "string" || options.source.includes("\0")) {
71
- throw new TypeError("copyKvToD1 source must be a namespace id");
72
- }
73
- const maxKeys = options.maxKeys ?? 500;
74
- if (!Number.isSafeInteger(maxKeys) || maxKeys < 1) {
75
- throw new TypeError("copyKvToD1 maxKeys must be a positive integer");
76
- }
77
- const allowed = new Set([...KEY_FAMILIES.map((family) => family.name), "connector-owned", "unclassified"]);
78
- if (options.overwriteFamilies?.some((family) => !allowed.has(family))) {
79
- throw new TypeError("copyKvToD1 overwriteFamilies contains an unknown family");
80
- }
81
- const overwrite = new Set(options.overwriteFamilies ?? []);
82
- const driver = nativeSqlDriver(d1Driver(db));
83
- const storage = nativeSqlStorage(driver);
84
- const remember = (kvCursor) => Effect.gen(function* () {
85
- const token = crypto.randomUUID();
86
- yield* storage.set(kvCopyKeys.cursor(token), JSON.stringify({ source: options.source, cursor: kvCursor }), {
87
- ttlSeconds: KV_COPY_CURSOR_TTL_SECONDS,
88
- });
89
- return token;
90
- });
91
- let cursor;
92
- let pageCursor;
93
- const families = {};
94
- const count = (key, field) => {
95
- (families[familyOfKey(key)] ??= emptyCounts())[field] += 1;
96
- };
97
- const unknown = new TypeError("copyKvToD1 cursor is unknown, expired, spent, or belongs to another source; restart under maintenance");
98
- const stale = new TypeError("copyKvToD1 refuses stale KV after cutover");
99
- // Validate before any D1 access so a failing binding cannot return raw input
100
- // as an error's resume point.
101
- if (options.cursor !== undefined &&
102
- !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(options.cursor)) {
103
- throw unknown;
104
- }
105
- const failures = new WeakSet();
106
- const failure = (message) => {
107
- const error = new KvToD1CopyError(message, undefined);
108
- failures.add(error);
109
- return error;
110
- };
111
- const claim = yield* attempt(Effect.gen(function* () {
112
- if (!options.allowStale && (yield* storage.get(kvCutoverKeys.source(options.source))) !== null) {
113
- throw stale;
114
- }
115
- if (options.cursor !== undefined) {
116
- const key = kvCopyKeys.cursor(options.cursor);
117
- const stored = yield* storage.get(key);
118
- let record = null;
119
- try {
120
- record = stored === null ? null : JSON.parse(stored);
121
- }
122
- catch {
123
- /* refused below */
124
- }
125
- if (!record || record.source !== options.source || typeof record.cursor !== "string") {
126
- throw unknown;
127
- }
128
- // Delete only the exact record read: concurrent consumers cannot both win.
129
- if (!(yield* storage.compareAndSet(key, stored, null)))
130
- throw unknown;
131
- cursor = record.cursor;
132
- }
133
- }));
134
- if (claim._tag === "Failure") {
135
- const error = claim.failure;
136
- if (error === unknown || error === stale)
137
- throw error;
138
- throw new KvToD1CopyError(`Workers KV to D1 copy stopped: claiming the resume point in D1 failed${errorClass(error)}`, options.cursor);
139
- }
140
- const flush = (buffer) => Effect.gen(function* () {
141
- const entries = buffer.filter((entry) => {
142
- if (entry.expiresAtMs !== null && entry.expiresAtMs <= Date.now()) {
143
- count(entry.key, "expired");
144
- return false;
145
- }
146
- return true;
147
- });
148
- if (!entries.length)
149
- return;
150
- const flushed = yield* attempt(Effect.gen(function* () {
151
- if (options.verify) {
152
- for (const entry of entries) {
153
- const [row] = yield* driver.all({
154
- // Read bytes, including NUL, and hash in JS. D1 has no SHA-256 function.
155
- sql: "SELECT CAST(value AS BLOB) AS value_bytes, expires_at_ms FROM connecta_kv WHERE key = ?",
156
- params: [entry.key],
157
- });
158
- const bytes = row ? Uint8Array.from(row.value_bytes) : undefined;
159
- const hash = bytes ? yield* leaf(() => crypto.subtle.digest("SHA-256", bytes)) : undefined;
160
- const hex = hash
161
- ? Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("")
162
- : undefined;
163
- count(entry.key, row && row.expires_at_ms === entry.expiresAtMs && hex === (yield* valueHash(entry.value))
164
- ? "verified"
165
- : "mismatches");
166
- }
167
- }
168
- else {
169
- // Partition only this bounded buffer by overwrite permission.
170
- for (const replace of [false, true]) {
171
- const group = entries.filter((entry) => overwrite.has(familyOfKey(entry.key)) === replace);
172
- if (!group.length)
173
- continue;
174
- const outcomes = yield* copyIntoSqlEffect(driver, group, { overwrite: replace, now: Date.now() });
175
- for (const [index, outcome] of outcomes.entries())
176
- count(group[index].key, OUTCOME_COUNT[outcome]);
177
- }
178
- }
179
- }));
180
- if (flushed._tag === "Failure") {
181
- const error = flushed.failure;
182
- throw failure(`Workers KV to D1 copy stopped: ${options.verify ? "verifying" : "writing to"} D1 failed${errorClass(error)}`);
183
- }
184
- });
185
- let read = 0;
186
- const copied = yield* attempt(Effect.gen(function* () {
187
- for (;;) {
188
- pageCursor = cursor;
189
- const listed = yield* attempt(leaf(() => kv.list({ limit: Math.min(KV_LIST_LIMIT, maxKeys - read), ...(cursor ? { cursor } : {}) })));
190
- if (listed._tag === "Failure") {
191
- const error = listed.failure;
192
- throw failure(`Workers KV to D1 copy stopped: listing Workers KV failed${errorClass(error)}`);
193
- }
194
- const page = listed.success;
195
- read += page.keys.length;
196
- let entries = [];
197
- let bufferedBytes = 0;
198
- for (const key of page.keys) {
199
- const keyBytes = encoder.encode(key.name).byteLength;
200
- if (key.name.includes("\0") || keyBytes + ROW_OVERHEAD_BYTES > D1_MAX_ROW_BYTES) {
201
- count(key.name, "invalid");
202
- continue;
203
- }
204
- const expiresAtMs = key.expiration === undefined ? null : key.expiration * 1000;
205
- if (expiresAtMs !== null && expiresAtMs <= Date.now()) {
206
- count(key.name, "expired");
207
- continue;
208
- }
209
- const readValue = yield* attempt(leaf(() => kv.get(key.name, "text")));
210
- if (readValue._tag === "Failure") {
211
- const error = readValue.failure;
212
- throw failure(`Workers KV to D1 copy stopped: reading an entry of the ${familyOfKey(key.name)} family from Workers KV failed${errorClass(error)}`);
213
- }
214
- const value = readValue.success;
215
- if (value === null || (expiresAtMs !== null && expiresAtMs <= Date.now())) {
216
- count(key.name, "expired");
217
- continue;
218
- }
219
- const valueBytes = encoder.encode(value).byteLength;
220
- const bytes = keyBytes + valueBytes + ROW_OVERHEAD_BYTES;
221
- if (valueBytes > D1_MAX_ROW_BYTES || bytes > D1_MAX_ROW_BYTES) {
222
- count(key.name, "invalid");
223
- continue;
224
- }
225
- if (entries.length >= COPY_GROUP_KEYS || bufferedBytes + bytes > COPY_BUFFER_BYTES) {
226
- yield* flush(entries);
227
- entries = [];
228
- bufferedBytes = 0;
229
- }
230
- entries.push({ key: key.name, value, expiresAtMs });
231
- bufferedBytes += bytes;
232
- }
233
- yield* flush(entries);
234
- if (page.list_complete || !page.cursor)
235
- return { done: true, families };
236
- cursor = page.cursor;
237
- if (read >= maxKeys)
238
- return { done: false, cursor: yield* remember(cursor), families };
239
- }
240
- }));
241
- if (copied._tag === "Failure") {
242
- const error = copied.failure;
243
- // The old token was claimed atomically. Replace it on failure, if D1 works.
244
- const resume = pageCursor === undefined
245
- ? undefined
246
- : yield* attempt(remember(pageCursor)).pipe(Effect.map((result) => (result._tag === "Success" ? result.success : undefined)));
247
- throw new KvToD1CopyError(error instanceof KvToD1CopyError && failures.has(error)
248
- ? error.message
249
- : `Workers KV to D1 copy stopped: recording the resume point in D1 failed${errorClass(error)}`, resume);
250
- }
251
- else {
252
- return copied.success;
253
- }
254
- });
255
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * The key-value table. Its shape is the one the 0.28 Worker example's
3
- * `d1-storage.ts` documented, so a D1 database that already holds it is read
4
- * as is.
5
- */
6
- export declare const KV_SCHEMA: readonly string[];