@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.
Files changed (320) hide show
  1. package/CHANGELOG.md +961 -5
  2. package/README.md +62 -39
  3. package/bin/connecta.mjs +110 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1632 -1509
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +526 -0
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +220 -0
  52. package/dist/connectors/api-connector.js +214 -0
  53. package/dist/connectors/api.d.ts +8 -71
  54. package/dist/connectors/api.js +13 -99
  55. package/dist/connectors/guarded-fetch.d.ts +144 -0
  56. package/dist/connectors/guarded-fetch.js +108 -25
  57. package/dist/connectors/negotiation-cache.js +46 -0
  58. package/dist/connectors/option-shapes.js +27 -0
  59. package/dist/connectors/remote-mcp.d.ts +30 -4
  60. package/dist/connectors/remote-mcp.js +1780 -351
  61. package/dist/connectors/resource-uri.js +335 -0
  62. package/dist/credential-contract.d.ts +10 -0
  63. package/dist/credential-rules.js +1 -3
  64. package/dist/credential-url.js +108 -0
  65. package/dist/credentials.d.ts +6 -1
  66. package/dist/credentials.js +27 -18
  67. package/dist/d1.d.ts +130 -0
  68. package/dist/d1.js +290 -0
  69. package/dist/describe-config.d.ts +165 -0
  70. package/dist/describe-config.js +315 -0
  71. package/dist/described.js +106 -0
  72. package/dist/downstream-input-context.js +119 -0
  73. package/dist/downstream-input.js +323 -0
  74. package/dist/errors.d.ts +97 -20
  75. package/dist/errors.js +158 -80
  76. package/dist/execute.js +453 -305
  77. package/dist/executor-admission.d.ts +1 -0
  78. package/dist/executor-admission.js +11 -15
  79. package/dist/executor-contract.d.ts +20 -0
  80. package/dist/executor-contract.js +48 -0
  81. package/dist/executor-result.js +24 -9
  82. package/dist/executors/quickjs-child.js +3 -4
  83. package/dist/executors/quickjs-runtime.js +147 -59
  84. package/dist/executors/quickjs.js +29 -35
  85. package/dist/favicon.js +1 -2
  86. package/dist/fix-prompt.js +1 -3
  87. package/dist/guest-runtime.js +188 -0
  88. package/dist/guest-types.d.ts +176 -0
  89. package/dist/guest-types.js +1 -0
  90. package/dist/html-security.js +24 -0
  91. package/dist/identity.js +1 -3
  92. package/dist/inbound-credential.js +52 -0
  93. package/dist/index.d.ts +25 -257
  94. package/dist/index.js +65 -478
  95. package/dist/invocation-auth.js +69 -0
  96. package/dist/invocation.js +230 -73
  97. package/dist/json-schema.d.ts +1 -1
  98. package/dist/mcp-client-context.js +35 -0
  99. package/dist/mcp-result.js +11 -7
  100. package/dist/meta-output.js +76 -0
  101. package/dist/meta-tool-names.d.ts +2 -0
  102. package/dist/meta-tool-names.js +3 -0
  103. package/dist/meta-tools.js +285 -209
  104. package/dist/module-contracts.d.ts +8 -23
  105. package/dist/node.d.ts +0 -2
  106. package/dist/node.js +2 -2
  107. package/dist/oauth-callback-outcome.js +1 -3
  108. package/dist/oauth-handoff.js +117 -0
  109. package/dist/oauth-partition.js +17 -0
  110. package/dist/oauth-sealing.js +20 -0
  111. package/dist/operator-record.d.ts +137 -0
  112. package/dist/operator-record.js +420 -0
  113. package/dist/operator-ui/assets.js +31 -0
  114. package/dist/operator-ui/contract.d.ts +72 -0
  115. package/dist/operator-ui/contract.js +1 -0
  116. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  117. package/dist/operator-ui/generated.js +5 -4
  118. package/dist/operator-ui/model.d.ts +12 -87
  119. package/dist/operator-ui/model.js +2 -9
  120. package/dist/operator-ui/setup-commands.js +1 -3
  121. package/dist/operator-ui/view.d.ts +323 -0
  122. package/dist/operator-ui/view.js +62 -189
  123. package/dist/page-styles.js +2 -3
  124. package/dist/program-source.js +14 -2
  125. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  126. package/dist/provider.d.ts +142 -0
  127. package/dist/provider.js +187 -0
  128. package/dist/providers/_shared/google/result-size.js +57 -0
  129. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  130. package/dist/providers/_shared/google/service-account.js +500 -0
  131. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  132. package/dist/providers/_shared/google/workspace.js +518 -0
  133. package/dist/providers/basecamp/index.d.ts +43 -0
  134. package/dist/providers/basecamp/index.js +997 -0
  135. package/dist/providers/basecamp/skill.generated.js +9 -0
  136. package/dist/providers/breeze/index.d.ts +34 -0
  137. package/dist/providers/breeze/index.js +1446 -0
  138. package/dist/providers/breeze/skill.generated.js +9 -0
  139. package/dist/providers/ccb/index.d.ts +103 -0
  140. package/dist/providers/ccb/index.js +1725 -0
  141. package/dist/providers/ccb/skill.generated.js +9 -0
  142. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  143. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  144. package/dist/providers/cloudflare/skill.generated.js +10 -0
  145. package/dist/providers/docs/index.d.ts +54 -0
  146. package/dist/providers/docs/index.js +1237 -0
  147. package/dist/providers/docs/skill.generated.js +9 -0
  148. package/dist/providers/drive/index.d.ts +53 -0
  149. package/dist/providers/drive/index.js +1523 -0
  150. package/dist/providers/drive/skill.generated.js +9 -0
  151. package/dist/providers/forms/index.d.ts +54 -0
  152. package/dist/providers/forms/index.js +1217 -0
  153. package/dist/providers/forms/skill.generated.js +11 -0
  154. package/dist/providers/github/auth.d.ts +32 -0
  155. package/dist/providers/github/auth.js +361 -0
  156. package/dist/providers/github/hosted.js +218 -0
  157. package/dist/providers/github/index.d.ts +13 -0
  158. package/dist/providers/github/index.js +140 -0
  159. package/dist/providers/github/key.js +100 -0
  160. package/dist/providers/github/rest.js +321 -0
  161. package/dist/providers/github/scope.d.ts +29 -0
  162. package/dist/providers/github/scope.js +124 -0
  163. package/dist/providers/github/skill.generated.js +8 -0
  164. package/dist/providers/gmail/index.d.ts +51 -0
  165. package/dist/providers/gmail/index.js +1436 -0
  166. package/dist/providers/gmail/skill.generated.js +9 -0
  167. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  168. package/dist/providers/linear/index.js +352 -0
  169. package/dist/providers/linear/skill.generated.js +8 -0
  170. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  171. package/dist/providers/mixpanel/index.js +416 -0
  172. package/dist/providers/mixpanel/skill.generated.js +9 -0
  173. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  174. package/dist/providers/{notion.js → notion/index.js} +226 -239
  175. package/dist/providers/notion/skill.generated.js +10 -0
  176. package/dist/providers/overflow/index.d.ts +60 -0
  177. package/dist/providers/overflow/index.js +1535 -0
  178. package/dist/providers/overflow/skill.generated.js +8 -0
  179. package/dist/providers/planning-center/index.d.ts +59 -0
  180. package/dist/providers/planning-center/index.js +2199 -0
  181. package/dist/providers/planning-center/skill.generated.js +10 -0
  182. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  183. package/dist/providers/revenuecat/index.js +557 -0
  184. package/dist/providers/revenuecat/skill.generated.js +11 -0
  185. package/dist/providers/sheets/index.d.ts +54 -0
  186. package/dist/providers/sheets/index.js +1237 -0
  187. package/dist/providers/sheets/skill.generated.js +10 -0
  188. package/dist/providers/slides/index.d.ts +53 -0
  189. package/dist/providers/slides/index.js +2645 -0
  190. package/dist/providers/slides/skill.generated.js +9 -0
  191. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  192. package/dist/providers/stripe/index.js +266 -0
  193. package/dist/providers/stripe/skill.generated.js +11 -0
  194. package/dist/providers/tithely/index.d.ts +49 -0
  195. package/dist/providers/tithely/index.js +1003 -0
  196. package/dist/providers/tithely/skill.generated.js +8 -0
  197. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  198. package/dist/providers/vercel/index.js +1038 -0
  199. package/dist/providers/vercel/mcp-ownership.js +212 -0
  200. package/dist/providers/vercel/skill.generated.js +11 -0
  201. package/dist/registry.d.ts +108 -171
  202. package/dist/registry.js +513 -925
  203. package/dist/request-state.js +21 -0
  204. package/dist/resource-read.js +100 -0
  205. package/dist/result-shapes.js +17 -49
  206. package/dist/retired-toolkits.js +1 -1
  207. package/dist/routes/access-tokens.js +3 -6
  208. package/dist/routes/activity-context.js +21 -0
  209. package/dist/routes/activity-disclosure.js +40 -0
  210. package/dist/routes/activity.js +59 -21
  211. package/dist/routes/actor-labels.js +3 -50
  212. package/dist/routes/connect.js +221 -0
  213. package/dist/routes/credentials.js +15 -27
  214. package/dist/routes/mcp.js +356 -130
  215. package/dist/routes/oauth-client-metadata.js +15 -0
  216. package/dist/routes/oauth-management.js +45 -107
  217. package/dist/routes/oauth.js +136 -57
  218. package/dist/routes/operator.js +3 -3
  219. package/dist/routes/shared.d.ts +25 -64
  220. package/dist/routes/shared.js +160 -80
  221. package/dist/routes/skills.js +30 -0
  222. package/dist/routes/ui-config.js +225 -0
  223. package/dist/routes/ui-data.js +40 -35
  224. package/dist/routes/ui.js +66 -56
  225. package/dist/runtime/admission.js +4 -2
  226. package/dist/runtime/call-admission.js +3 -1
  227. package/dist/runtime/connector-scope.js +1 -4
  228. package/dist/runtime/run.js +10 -39
  229. package/dist/runtime/services.js +12 -117
  230. package/dist/runtime/shared-read.js +1 -3
  231. package/dist/runtime/storage.js +8 -1
  232. package/dist/sent-secrets.d.ts +52 -0
  233. package/dist/sent-secrets.js +474 -0
  234. package/dist/server.js +28 -71
  235. package/dist/skills.js +406 -174
  236. package/dist/sqlite.d.ts +48 -0
  237. package/dist/sqlite.js +166 -0
  238. package/dist/storage/keys.js +428 -0
  239. package/dist/storage/memory.js +7 -0
  240. package/dist/storage/sql.d.ts +66 -0
  241. package/dist/storage/sql.js +534 -0
  242. package/dist/timeout.js +4 -2
  243. package/dist/tool-name.js +17 -0
  244. package/dist/tool-safety.d.ts +9 -35
  245. package/dist/tool-safety.js +20 -40
  246. package/dist/types.d.ts +320 -54
  247. package/dist/types.js +0 -1
  248. package/dist/ui.d.ts +7 -19
  249. package/dist/ui.js +76 -90
  250. package/dist/url-safety.js +3 -6
  251. package/dist/usage-guide.js +137 -0
  252. package/dist/validate.d.ts +7 -0
  253. package/dist/validate.js +309 -47
  254. package/dist/version.d.ts +1 -7
  255. package/dist/version.js +3 -7
  256. package/dist/worker.d.ts +11 -0
  257. package/dist/worker.js +434 -0
  258. package/documentation/architecture.md +665 -230
  259. package/documentation/auth.md +962 -458
  260. package/documentation/code-mode.md +501 -312
  261. package/documentation/meta-tools.md +449 -200
  262. package/documentation/operator-tests.md +93 -0
  263. package/documentation/provider-migration-0.29.md +60 -0
  264. package/examples/worker/AGENTS.md +34 -12
  265. package/examples/worker/README.md +286 -255
  266. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  267. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  268. package/examples/worker/src/connecta.config.ts +151 -0
  269. package/examples/worker/src/index.ts +14 -197
  270. package/examples/worker/wrangler.jsonc +15 -35
  271. package/package.json +125 -35
  272. package/templates/node/.dockerignore +1 -2
  273. package/templates/node/.env.example +20 -21
  274. package/templates/node/AGENTS.md +21 -17
  275. package/templates/node/Dockerfile +3 -5
  276. package/templates/node/README.md +133 -58
  277. package/templates/node/docker-compose.yml +11 -12
  278. package/templates/node/package.json +4 -2
  279. package/templates/node/src/connecta.config.ts +119 -0
  280. package/templates/node/src/index.ts +9 -154
  281. package/templates/node/src/provision-token.ts +17 -0
  282. package/AGENTS.md +0 -170
  283. package/dist/artifacts/connector.d.ts +0 -58
  284. package/dist/artifacts/connector.js +0 -730
  285. package/dist/artifacts/document.js +0 -86
  286. package/dist/artifacts/guide.js +0 -140
  287. package/dist/artifacts/html-scan.js +0 -329
  288. package/dist/artifacts/json.js +0 -78
  289. package/dist/artifacts/kv-store.d.ts +0 -20
  290. package/dist/artifacts/kv-store.js +0 -140
  291. package/dist/artifacts/markdown.js +0 -478
  292. package/dist/artifacts/module.d.ts +0 -37
  293. package/dist/artifacts/module.js +0 -66
  294. package/dist/artifacts/operations.d.ts +0 -197
  295. package/dist/artifacts/operations.js +0 -1002
  296. package/dist/artifacts/refresh.d.ts +0 -55
  297. package/dist/artifacts/refresh.js +0 -236
  298. package/dist/artifacts/routes.js +0 -206
  299. package/dist/artifacts/types.d.ts +0 -214
  300. package/dist/artifacts/types.js +0 -22
  301. package/dist/artifacts/validate.d.ts +0 -62
  302. package/dist/artifacts/validate.js +0 -651
  303. package/dist/artifacts.d.ts +0 -6
  304. package/dist/artifacts.js +0 -9
  305. package/dist/auth/bearer.d.ts +0 -11
  306. package/dist/auth/bearer.js +0 -50
  307. package/dist/providers/linear.js +0 -217
  308. package/dist/providers/mixpanel.js +0 -247
  309. package/dist/providers/revenuecat.js +0 -317
  310. package/dist/providers/stripe.js +0 -253
  311. package/dist/providers/vercel.js +0 -1253
  312. package/dist/storage/file.d.ts +0 -15
  313. package/dist/storage/file.js +0 -455
  314. package/ethos.md +0 -93
  315. package/examples/worker/src/cloudflare-kv.ts +0 -43
  316. package/examples/worker/src/d1-activity-row.ts +0 -155
  317. package/examples/worker/src/d1-activity.ts +0 -147
  318. package/examples/worker/src/d1-storage.ts +0 -138
  319. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  320. package/templates/node/src/file-activity.ts +0 -182
@@ -1,55 +0,0 @@
1
- import type { ArtifactOperations } from "./operations.js";
2
- import type { ArtifactHeadRecord, ArtifactRunRecord } from "./types.js";
3
- import type { RenderPage } from "./operations.js";
4
- /** Safe metadata only. Logs and downstream errors never enter the viewer. */
5
- export declare function freshnessOf(head: ArtifactHeadRecord, now?: number): {
6
- state: "unconfigured";
7
- } | {
8
- running?: boolean;
9
- last?: {
10
- runId: string;
11
- at: string;
12
- status: "succeeded" | "unchanged" | "failed" | "superseded";
13
- documentVersion?: number;
14
- };
15
- dueAt?: string;
16
- state: "stale" | "current";
17
- schedule: import("./types.js").ArtifactRefreshSchedule;
18
- document: string;
19
- programVersion: number;
20
- };
21
- export interface ArtifactRefreshRuntime {
22
- /** The core's bounded read-only program runner; no sandbox code enters this subpath. */
23
- execute(program: string, owner: NonNullable<ArtifactHeadRecord["refresh"]>["owner"], signal: AbortSignal): Promise<{
24
- content: {
25
- type: string;
26
- text?: string;
27
- }[];
28
- isError?: boolean;
29
- }>;
30
- claimMs: number;
31
- /** UI branding shared by the viewer and every render check. */
32
- branding?: import("../types.js").ConnectaBranding;
33
- }
34
- export type RefreshOutcome = {
35
- status: "skipped";
36
- } | {
37
- status: "succeeded" | "unchanged" | "failed" | "superseded";
38
- runId: string;
39
- documentVersion?: number;
40
- errorCode?: string;
41
- };
42
- export declare class ArtifactRefreshService {
43
- #private;
44
- constructor(ops: ArtifactOperations);
45
- bind(runtime: ArtifactRefreshRuntime): void;
46
- setRender(renderFor: (signal: AbortSignal) => RenderPage | undefined): void;
47
- run(id: string, trigger: ArtifactRunRecord["trigger"]): Promise<RefreshOutcome>;
48
- runDue(): Promise<{
49
- scanned: number;
50
- started: number;
51
- succeeded: number;
52
- failed: number;
53
- capped: boolean;
54
- }>;
55
- }
@@ -1,236 +0,0 @@
1
- // Deployment-triggered artifact refresh. No interval, cron, or background
2
- // fiber lives here: Node and Workers decide when to call runDue().
3
- const MAX_DUE_PER_TICK = 10;
4
- const MAX_HEADS_PER_TICK = 1_000;
5
- const MAX_MESSAGE_CHARS = 500;
6
- /** Safe metadata only. Logs and downstream errors never enter the viewer. */
7
- export function freshnessOf(head, now = Date.now()) {
8
- const refresh = head.refresh;
9
- if (!refresh)
10
- return { state: "unconfigured" };
11
- const period = refresh.schedule === "daily" ? 86_400_000
12
- : refresh.schedule === "weekly" ? 604_800_000 : undefined;
13
- const dueAt = period === undefined ? undefined
14
- : new Date(Date.parse(refresh.last?.at ?? refresh.configuredAt) + period).toISOString();
15
- const stale = refresh.last?.status === "failed" || refresh.last?.status === "superseded" ||
16
- (dueAt !== undefined && now >= Date.parse(dueAt));
17
- return {
18
- state: stale ? "stale" : "current",
19
- schedule: refresh.schedule,
20
- document: refresh.document,
21
- programVersion: refresh.program.version,
22
- ...(dueAt ? { dueAt } : {}),
23
- ...(refresh.last ? { last: refresh.last } : {}),
24
- ...(refresh.claim && Date.parse(refresh.claim.until) > now ? { running: true } : {}),
25
- };
26
- }
27
- function capture(text, bytes) {
28
- if (typeof text !== "string" || !text)
29
- return undefined;
30
- const encoder = new TextEncoder();
31
- let clipped = text.slice(0, bytes);
32
- while (encoder.encode(clipped).byteLength > bytes)
33
- clipped = clipped.slice(0, -1);
34
- return clipped;
35
- }
36
- function responsePayload(result) {
37
- const text = result.content.find((block) => block.type === "text")?.text;
38
- if (!text)
39
- return { error: { code: "executor_failed", message: "Refresh program returned no result." } };
40
- try {
41
- const parsed = JSON.parse(text);
42
- return parsed;
43
- }
44
- catch {
45
- return { error: { code: "executor_failed", message: result.isError ? text : "Refresh program returned an invalid result." } };
46
- }
47
- }
48
- async function beforeDeadline(work, signal) {
49
- if (signal.aborted)
50
- throw new Error("Refresh deadline expired.");
51
- let onAbort;
52
- const expired = new Promise((_resolve, reject) => {
53
- onAbort = () => reject(new Error("Refresh deadline expired."));
54
- signal.addEventListener("abort", onAbort, { once: true });
55
- });
56
- try {
57
- return await Promise.race([work, expired]);
58
- }
59
- finally {
60
- if (onAbort)
61
- signal.removeEventListener("abort", onAbort);
62
- }
63
- }
64
- export class ArtifactRefreshService {
65
- #ops;
66
- #runtime;
67
- #renderFor;
68
- constructor(ops) {
69
- this.#ops = ops;
70
- }
71
- bind(runtime) {
72
- if (this.#runtime)
73
- throw new Error("One artifacts() module cannot be bound to two deployments.");
74
- this.#runtime = runtime;
75
- }
76
- setRender(renderFor) {
77
- this.#renderFor = renderFor;
78
- }
79
- async run(id, trigger) {
80
- const runtime = this.#runtime;
81
- if (!runtime)
82
- throw new Error("Create a Connecta deployment with this artifacts() module before refreshing.");
83
- const runId = crypto.randomUUID();
84
- const claimed = await this.#ops.claimRefresh(id, runId, trigger, trigger === "schedule", runtime.claimMs);
85
- if (!claimed.ok) {
86
- if ("skipped" in claimed)
87
- return { status: "skipped" };
88
- throw new Error(claimed.message);
89
- }
90
- const run = claimed.run;
91
- const deadline = Date.parse(claimed.head.refresh.claim.until);
92
- const controller = new AbortController();
93
- const timeout = setTimeout(() => controller.abort(), Math.max(0, deadline - Date.now()));
94
- let outcome;
95
- try {
96
- await this.#ops.store.putRun(id, run);
97
- if (Date.now() >= deadline)
98
- throw new Error("Refresh claim expired before execution.");
99
- if (!claimed.program)
100
- throw new Error("Refresh program body is unavailable.");
101
- const program = await beforeDeadline(this.#ops.store.body(claimed.program), controller.signal);
102
- if (program === null)
103
- throw new Error("Refresh program body is unavailable.");
104
- const response = responsePayload(await beforeDeadline(runtime.execute(program, claimed.head.refresh.owner, controller.signal), controller.signal));
105
- const logs = capture(response.logs, this.#ops.context.limits.runLogBytes);
106
- if (logs)
107
- run.logs = logs;
108
- if (response.error) {
109
- run.status = "failed";
110
- run.errorCode = capture(response.error.code, 80) ?? "executor_failed";
111
- const message = capture(response.error.message, MAX_MESSAGE_CHARS);
112
- if (message)
113
- run.message = message;
114
- outcome = { status: "failed", runId, errorCode: run.errorCode };
115
- }
116
- else if (response.result === null || response.result === undefined) {
117
- run.status = "failed";
118
- run.errorCode = "invalid_result";
119
- run.message = "Refresh program must return a non-null JSON value.";
120
- outcome = { status: "failed", runId, errorCode: run.errorCode };
121
- }
122
- else {
123
- const document = claimed.head.refresh.document;
124
- const baseVersion = claimed.head.documents[document]?.version ?? 0;
125
- const render = this.#renderFor?.(controller.signal);
126
- const saved = await this.#ops.setDocuments({
127
- id,
128
- documents: { [document]: { baseVersion, value: response.result } },
129
- by: { kind: "refresh" },
130
- op: "refresh", runId,
131
- programVersion: run.programVersion,
132
- ...(render ? { render } : {}),
133
- });
134
- if (saved.ok) {
135
- run.status = "succeeded";
136
- const documentVersion = saved.head.documents[document]?.version;
137
- if (documentVersion !== undefined)
138
- run.documentVersion = documentVersion;
139
- outcome = { status: "succeeded", runId,
140
- ...(documentVersion !== undefined ? { documentVersion } : {}) };
141
- }
142
- else {
143
- run.status = saved.code === "conflict" ? "superseded" : "failed";
144
- run.errorCode = saved.code;
145
- const message = capture(saved.message, MAX_MESSAGE_CHARS);
146
- if (message)
147
- run.message = message;
148
- outcome = { status: run.status, runId, errorCode: run.errorCode };
149
- }
150
- }
151
- }
152
- catch (error) {
153
- run.status = "failed";
154
- run.errorCode = "refresh_failed";
155
- const message = capture(error instanceof Error ? error.message : String(error), MAX_MESSAGE_CHARS);
156
- if (message)
157
- run.message = message;
158
- outcome = { status: "failed", runId, errorCode: run.errorCode };
159
- }
160
- finally {
161
- clearTimeout(timeout);
162
- controller.abort();
163
- }
164
- run.finishedAt = new Date().toISOString();
165
- await this.#ops.finishRefresh(id, run);
166
- return outcome;
167
- }
168
- async runDue() {
169
- if (!this.#runtime)
170
- throw new Error("Create a Connecta deployment before running refreshes.");
171
- let after = await this.#ops.store.refreshScanCursor();
172
- let scanned = 0;
173
- let started = 0;
174
- let succeeded = 0;
175
- let failed = 0;
176
- while (scanned < MAX_HEADS_PER_TICK && started < MAX_DUE_PER_TICK) {
177
- const limit = Math.min(100, MAX_HEADS_PER_TICK - scanned);
178
- const page = await this.#ops.store.heads({
179
- ...(after ? { after } : {}),
180
- limit,
181
- });
182
- if (page.next !== undefined && after !== undefined && page.next <= after) {
183
- throw new Error("Artifact head scan did not advance.");
184
- }
185
- if (!page.heads.length) {
186
- if (page.next) {
187
- after = page.next;
188
- scanned += limit;
189
- continue;
190
- }
191
- after = undefined;
192
- break;
193
- }
194
- for (const { id, head } of page.heads) {
195
- scanned++;
196
- after = id;
197
- if (!head.refresh || head.archived)
198
- continue;
199
- let outcome;
200
- try {
201
- outcome = await this.run(id, "schedule");
202
- }
203
- catch {
204
- // One broken record or transient run write does not starve later heads.
205
- started++;
206
- failed++;
207
- if (started >= MAX_DUE_PER_TICK)
208
- break;
209
- continue;
210
- }
211
- if (outcome.status === "skipped")
212
- continue;
213
- started++;
214
- if (outcome.status === "succeeded" || outcome.status === "unchanged")
215
- succeeded++;
216
- else
217
- failed++;
218
- if (started >= MAX_DUE_PER_TICK)
219
- break;
220
- }
221
- if (page.next) {
222
- scanned += limit - page.heads.length;
223
- if (started < MAX_DUE_PER_TICK)
224
- after = page.next;
225
- }
226
- if (!page.next || started >= MAX_DUE_PER_TICK) {
227
- if (!page.next && started < MAX_DUE_PER_TICK && scanned < MAX_HEADS_PER_TICK)
228
- after = undefined;
229
- break;
230
- }
231
- }
232
- await this.#ops.store.setRefreshScanCursor(after);
233
- return { scanned, started, succeeded, failed,
234
- capped: scanned >= MAX_HEADS_PER_TICK || started >= MAX_DUE_PER_TICK };
235
- }
236
- }
@@ -1,206 +0,0 @@
1
- // The routes behind artifact pages: a JSON API the operator shell calls with
2
- // its session, and one constant, data-free frame the page renders in.
3
- //
4
- // The operator UI authenticates with a header, not a cookie — a bearer from
5
- // localStorage or a Clerk session token — so a bare navigation to a page
6
- // carries no credential. The trusted shell therefore fetches the page from
7
- // `_api` and hands it to the frame with `postMessage`; the frame itself is
8
- // served to anyone and holds nothing. What makes it safe is its response
9
- // header: `sandbox allow-scripts` without `allow-same-origin` gives the page
10
- // an opaque origin — no cookies, no storage, no route of this deployment it
11
- // could call — and the fetch directives leave it no network.
12
- import { resolveActorLabels, actorKey } from "../routes/actor-labels.js";
13
- import { authorize, mayViewArtifacts, privateJson, validateAuthPermissions, } from "../routes/shared.js";
14
- import { resolveBranding, schemeAttribute, themeCss } from "../branding.js";
15
- import { TOKENS_CSS } from "../page-styles.js";
16
- import { frameCsp } from "./document.js";
17
- import { scriptSafeJson } from "./json.js";
18
- import { freshnessOf } from "./refresh.js";
19
- import { ARTIFACT_ID } from "./validate.js";
20
- const LIBRARY_PAGE = 50;
21
- const MAX_QUERY_CHARS = 200;
22
- const MAX_PINS = 32;
23
- const PIN = /^([A-Za-z_][A-Za-z0-9_]{0,63}):(\d{1,9})$/;
24
- const notFound = () => privateJson({ error: "not found" }, { status: 404 });
25
- /** How long the frame waits for its document before saying it did not come. */
26
- const FRAME_STALL_MS = 15_000;
27
- /**
28
- * The frame's bootstrap: wait for exactly one document from the parent, then
29
- * become it.
30
- *
31
- * Until the document arrives the frame shows one quiet line on the viewer's
32
- * own surface, in the deployment's scheme and tokens, instead of a blank white
33
- * box; if the handshake never completes, the line says so. None of it is page
34
- * data — the scheme and tokens are deployment configuration — and all of it is
35
- * gone the moment `document.open` replaces this document with the page.
36
- */
37
- function frameBootstrap(expectedOrigin, theme) {
38
- return `<!doctype html>
39
- <html lang="en"${schemeAttribute(theme)}><head><meta charset="utf-8"><meta name="referrer" content="no-referrer">
40
- <style>${TOKENS_CSS}${themeCss(theme)}html{background:var(--surface)}body{color:var(--muted);font:.875rem/1.5 var(--sans);margin:0;padding:1.25rem 1.5rem}</style>
41
- </head><body>
42
- <p id="frame-status" role="status">Loading page…</p>
43
- <script>
44
- (() => {
45
- const expected = ${scriptSafeJson(expectedOrigin)};
46
- if (window.parent === window) return;
47
- const status = document.getElementById("frame-status");
48
- const stalled = setTimeout(() => {
49
- status.textContent = "This page did not load. Reload to try again.";
50
- }, ${FRAME_STALL_MS});
51
- const receive = (event) => {
52
- if (event.source !== window.parent || event.origin !== expected) return;
53
- const message = event.data;
54
- if (!message || message.type !== "document" || typeof message.html !== "string") return;
55
- window.removeEventListener("message", receive);
56
- clearTimeout(stalled);
57
- document.open();
58
- document.write(message.html);
59
- document.close();
60
- };
61
- window.addEventListener("message", receive);
62
- window.parent.postMessage({ type: "ready" }, expected);
63
- })();
64
- </script>
65
- </body></html>`;
66
- }
67
- export function artifactRoutes(options) {
68
- const ops = options.operations;
69
- const csp = frameCsp(options.allowlist);
70
- /** The viewer's identity, or the Response that refuses it. */
71
- const admit = async (context) => {
72
- const { request, baseUrl, opts, runtimeContext } = context;
73
- if (opts.auth.length === 0) {
74
- // An open deployment has nobody to show a team page to.
75
- return privateJson({ error: "artifact pages need inbound authentication" }, { status: 403 });
76
- }
77
- const authz = await authorize(request, baseUrl, opts.auth, runtimeContext, opts.identity, false);
78
- if (!authz.ok)
79
- return authz.response;
80
- try {
81
- validateAuthPermissions(authz, opts.registry);
82
- }
83
- catch {
84
- return privateJson({ error: "invalid identity permission" }, { status: 403 });
85
- }
86
- // Refused like an absent page, so a slug's existence is not an oracle.
87
- return mayViewArtifacts(authz, opts.registry) ? authz : notFound();
88
- };
89
- const labelsFor = async (context, actors) => {
90
- const labels = await resolveActorLabels(actors, context.opts.auth);
91
- return (actor) => ({
92
- label: labels.get(actorKey(actor)) ?? actor.id ?? actor.kind,
93
- });
94
- };
95
- const library = async (context) => {
96
- const admitted = await admit(context);
97
- if (admitted instanceof Response)
98
- return admitted;
99
- const params = context.url.searchParams;
100
- const query = (params.get("q") ?? "").slice(0, MAX_QUERY_CHARS);
101
- const cursor = params.get("cursor") ?? undefined;
102
- const listed = await ops.list({
103
- limit: LIBRARY_PAGE,
104
- ...(query ? { query } : {}),
105
- ...(params.get("archived") === "1" ? { includeArchived: true } : {}),
106
- ...(cursor !== undefined ? { cursor } : {}),
107
- });
108
- if (!listed.ok)
109
- return privateJson({ error: "invalid cursor" }, { status: 400 });
110
- const label = await labelsFor(context, listed.items.map(({ head }) => head.updatedBy));
111
- return privateJson({
112
- artifacts: listed.items.map(({ id, head }) => ({
113
- id,
114
- title: head.title,
115
- kind: head.kind,
116
- viewVersion: head.view.version,
117
- updatedAt: head.updatedAt,
118
- updatedBy: label(head.updatedBy),
119
- archived: head.archived,
120
- freshness: freshnessOf(head),
121
- })),
122
- ...(listed.nextCursor !== undefined ? { nextCursor: listed.nextCursor } : {}),
123
- });
124
- };
125
- const view = async (context, id) => {
126
- const admitted = await admit(context);
127
- if (admitted instanceof Response)
128
- return admitted;
129
- const params = context.url.searchParams;
130
- const pinned = params.get("v");
131
- let pin;
132
- if (pinned !== null) {
133
- const pins = params.getAll("d");
134
- if (!/^\d{1,9}$/.test(pinned) || pins.length > MAX_PINS)
135
- return notFound();
136
- const documents = {};
137
- for (const entry of pins) {
138
- const match = PIN.exec(entry);
139
- if (!match?.[1] || match[1] === "__proto__")
140
- return notFound();
141
- documents[match[1]] = Number(match[2]);
142
- }
143
- pin = { view: Number(pinned), documents };
144
- }
145
- const theme = resolveBranding(context.opts.branding).theme;
146
- const page = await ops.page(id, pin ?? {}, theme);
147
- if (!page.ok)
148
- return notFound();
149
- const origin = new URL(context.baseUrl).origin;
150
- const url = `${origin}/artifacts/${id}`;
151
- const documents = Object.entries(page.documents).map(([name, { record }]) => ({
152
- name,
153
- version: record.version,
154
- updatedAt: record.at,
155
- }));
156
- const pins = documents.map(({ name, version }) => `d=${name}:${version}`);
157
- const label = await labelsFor(context, [page.view.by]);
158
- return privateJson({
159
- id,
160
- title: page.head.title,
161
- kind: page.head.kind,
162
- archived: page.head.archived,
163
- snapshot: pin !== undefined,
164
- latestViewVersion: page.head.view.version,
165
- view: {
166
- version: page.view.version,
167
- at: page.view.at,
168
- by: label(page.view.by),
169
- },
170
- documents,
171
- freshness: freshnessOf(page.head),
172
- url,
173
- snapshotUrl: `${url}/v/${page.view.version}${pins.length ? `?${pins.join("&")}` : ""}`,
174
- document: page.document,
175
- });
176
- };
177
- return async (context) => {
178
- const { path, request } = context;
179
- const read = request.method === "GET" || request.method === "HEAD";
180
- if (path === "/artifacts/_frame") {
181
- if (!read)
182
- return privateJson({ error: "method not allowed" }, { status: 405 });
183
- const bootstrap = frameBootstrap(new URL(context.baseUrl).origin, resolveBranding(context.opts.branding).theme);
184
- return new Response(request.method === "HEAD" ? null : bootstrap, {
185
- headers: {
186
- "Content-Type": "text/html; charset=utf-8",
187
- "Content-Security-Policy": csp,
188
- "X-Content-Type-Options": "nosniff",
189
- "Cache-Control": "no-store",
190
- "Cross-Origin-Resource-Policy": "same-origin",
191
- "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=()",
192
- },
193
- });
194
- }
195
- if (!path.startsWith("/artifacts/_api/"))
196
- return null;
197
- if (request.method !== "GET")
198
- return privateJson({ error: "method not allowed" }, { status: 405 });
199
- if (path === "/artifacts/_api/list")
200
- return library(context);
201
- const match = /^\/artifacts\/_api\/view\/([^/]+)$/.exec(path);
202
- if (match?.[1] && ARTIFACT_ID.test(match[1]))
203
- return view(context, match[1]);
204
- return notFound();
205
- };
206
- }