@fungi.computer/oyster 0.0.0-stage → 0.1.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 (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1134 -2
  3. package/dist/agents.d.ts +114 -0
  4. package/dist/agents.js +322 -0
  5. package/dist/app-builds.d.ts +210 -0
  6. package/dist/app-builds.js +499 -0
  7. package/dist/app-sandbox.d.ts +2 -0
  8. package/dist/app-sandbox.js +2 -0
  9. package/dist/app-sources.d.ts +31 -0
  10. package/dist/app-sources.js +370 -0
  11. package/dist/app-storage.d.ts +141 -0
  12. package/dist/app-storage.js +281 -0
  13. package/dist/apps/gate.d.ts +105 -0
  14. package/dist/apps/gate.js +273 -0
  15. package/dist/apps/index.d.ts +5 -0
  16. package/dist/apps/index.js +5 -0
  17. package/dist/apps/installation.d.ts +248 -0
  18. package/dist/apps/installation.js +307 -0
  19. package/dist/apps/operations.d.ts +8 -0
  20. package/dist/apps/operations.js +98 -0
  21. package/dist/apps/tools.d.ts +33 -0
  22. package/dist/apps/tools.js +17 -0
  23. package/dist/auth.d.ts +11 -0
  24. package/dist/auth.js +93 -0
  25. package/dist/backend-request.d.ts +3 -0
  26. package/dist/backend-request.js +53 -0
  27. package/dist/build/artifact-writer.d.ts +40 -0
  28. package/dist/build/artifact-writer.js +181 -0
  29. package/dist/build/build-jobs.d.ts +128 -0
  30. package/dist/build/build-jobs.js +203 -0
  31. package/dist/build/build-policy.d.ts +10 -0
  32. package/dist/build/build-policy.js +10 -0
  33. package/dist/build/build-step.d.ts +37 -0
  34. package/dist/build/build-step.js +28 -0
  35. package/dist/build/bundled-html.d.ts +7 -0
  36. package/dist/build/bundled-html.js +64 -0
  37. package/dist/build/candidate-builder.d.ts +68 -0
  38. package/dist/build/candidate-builder.js +91 -0
  39. package/dist/build/candidate-cli.d.ts +1 -0
  40. package/dist/build/candidate-cli.js +119 -0
  41. package/dist/build/candidate-cli.mjs +28170 -0
  42. package/dist/build/content-objects.d.ts +37 -0
  43. package/dist/build/content-objects.js +53 -0
  44. package/dist/build/definition-emission.d.ts +34 -0
  45. package/dist/build/definition-emission.js +201 -0
  46. package/dist/build/effect.d.ts +249 -0
  47. package/dist/build/effect.js +436 -0
  48. package/dist/build/git-environment.d.ts +9 -0
  49. package/dist/build/git-environment.js +60 -0
  50. package/dist/build/html-recipe.d.ts +20 -0
  51. package/dist/build/html-recipe.js +51 -0
  52. package/dist/build/local-build.d.ts +67 -0
  53. package/dist/build/local-build.js +1128 -0
  54. package/dist/build/snapshot-package-store.d.ts +12 -0
  55. package/dist/build/snapshot-package-store.js +36 -0
  56. package/dist/build/snapshot-program-scripts.d.ts +6 -0
  57. package/dist/build/snapshot-program-scripts.js +2 -0
  58. package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
  59. package/dist/build/snapshot-programs/snapshot-path.js +6 -0
  60. package/dist/build/submission-contract.d.ts +828 -0
  61. package/dist/build/submission-contract.js +180 -0
  62. package/dist/build/workspace-build.d.ts +55 -0
  63. package/dist/build/workspace-build.js +200 -0
  64. package/dist/build-contract.d.ts +7 -0
  65. package/dist/build-contract.js +7 -0
  66. package/dist/build-egress.d.ts +62 -0
  67. package/dist/build-egress.js +148 -0
  68. package/dist/build-jobs.d.ts +2 -0
  69. package/dist/build-jobs.js +2 -0
  70. package/dist/build.d.ts +12 -0
  71. package/dist/build.js +14 -0
  72. package/dist/capability-descriptor.d.ts +46 -0
  73. package/dist/capability-descriptor.js +51 -0
  74. package/dist/catalog-frame.d.ts +39 -0
  75. package/dist/catalog-frame.js +72 -0
  76. package/dist/cause-class.d.ts +19 -0
  77. package/dist/cause-class.js +54 -0
  78. package/dist/cloudflare.d.ts +123 -0
  79. package/dist/cloudflare.js +391 -0
  80. package/dist/computer-terminal.d.ts +38 -0
  81. package/dist/computer-terminal.js +410 -0
  82. package/dist/computer.d.ts +60 -0
  83. package/dist/computer.js +33 -0
  84. package/dist/event-recipients.d.ts +57 -0
  85. package/dist/event-recipients.js +144 -0
  86. package/dist/facet-invocation.d.ts +4 -0
  87. package/dist/facet-invocation.js +45 -0
  88. package/dist/files.d.ts +42 -0
  89. package/dist/files.js +123 -0
  90. package/dist/frame-binding-wire-script.d.ts +2 -0
  91. package/dist/frame-binding-wire-script.js +1 -0
  92. package/dist/frame-binding-wire.d.ts +28 -0
  93. package/dist/frame-binding-wire.js +54 -0
  94. package/dist/git-admission.d.ts +52 -0
  95. package/dist/git-admission.js +188 -0
  96. package/dist/git-auth.d.ts +11 -0
  97. package/dist/git-auth.js +27 -0
  98. package/dist/git-door.d.ts +35 -0
  99. package/dist/git-door.js +108 -0
  100. package/dist/git-journal.d.ts +31 -0
  101. package/dist/git-journal.js +188 -0
  102. package/dist/git.d.ts +31 -0
  103. package/dist/git.js +64 -0
  104. package/dist/guest-policy.d.ts +9 -0
  105. package/dist/guest-policy.js +13 -0
  106. package/dist/host.d.ts +244 -0
  107. package/dist/host.js +1043 -0
  108. package/dist/index.d.ts +166 -0
  109. package/dist/index.js +320 -0
  110. package/dist/installation-consent.d.ts +79 -0
  111. package/dist/installation-consent.js +82 -0
  112. package/dist/internal/background-origins.d.ts +3 -0
  113. package/dist/internal/background-origins.js +8 -0
  114. package/dist/internal/delivery.d.ts +12 -0
  115. package/dist/internal/delivery.js +167 -0
  116. package/dist/internal/host-gate.d.ts +14 -0
  117. package/dist/internal/host-gate.js +62 -0
  118. package/dist/internal/registry-installations.d.ts +6 -0
  119. package/dist/internal/registry-installations.js +12 -0
  120. package/dist/invocation.d.ts +251 -0
  121. package/dist/invocation.js +377 -0
  122. package/dist/keys.d.ts +48 -0
  123. package/dist/keys.js +52 -0
  124. package/dist/mcp-owner.d.ts +27 -0
  125. package/dist/mcp-owner.js +155 -0
  126. package/dist/mcp.d.ts +89 -0
  127. package/dist/mcp.js +429 -0
  128. package/dist/media.d.ts +23 -0
  129. package/dist/media.js +12 -0
  130. package/dist/pack.d.ts +79 -0
  131. package/dist/pack.js +151 -0
  132. package/dist/page/capabilities.d.ts +23 -0
  133. package/dist/page/capabilities.js +256 -0
  134. package/dist/page/computer-handles.d.ts +79 -0
  135. package/dist/page/computer-handles.js +44 -0
  136. package/dist/page/gill-observation.d.ts +4 -0
  137. package/dist/page/gill-observation.js +31 -0
  138. package/dist/page/lifetime.d.ts +6 -0
  139. package/dist/page/lifetime.js +19 -0
  140. package/dist/page/mount.d.ts +163 -0
  141. package/dist/page/mount.js +390 -0
  142. package/dist/page/storage.d.ts +3 -0
  143. package/dist/page/storage.js +82 -0
  144. package/dist/page/terminal.d.ts +5 -0
  145. package/dist/page/terminal.js +7 -0
  146. package/dist/page/window-launch.d.ts +18 -0
  147. package/dist/page/window-launch.js +11 -0
  148. package/dist/page-script.d.ts +14 -0
  149. package/dist/page-script.js +5 -0
  150. package/dist/r2-files.d.ts +3 -0
  151. package/dist/r2-files.js +79 -0
  152. package/dist/release.d.ts +83 -0
  153. package/dist/release.js +167 -0
  154. package/dist/root-data.d.ts +86 -0
  155. package/dist/root-data.js +69 -0
  156. package/dist/shell/app-directory.d.ts +542 -0
  157. package/dist/shell/app-directory.js +199 -0
  158. package/dist/shell/browser-actions.d.ts +7 -0
  159. package/dist/shell/browser-actions.js +56 -0
  160. package/dist/shell/command-host.d.ts +112 -0
  161. package/dist/shell/command-host.js +502 -0
  162. package/dist/shell/commands.d.ts +25 -0
  163. package/dist/shell/commands.js +78 -0
  164. package/dist/shell/index.d.ts +8 -0
  165. package/dist/shell/index.js +8 -0
  166. package/dist/shell/keys.d.ts +41 -0
  167. package/dist/shell/keys.js +134 -0
  168. package/dist/shell/shell-module.d.ts +55 -0
  169. package/dist/shell/shell-module.js +86 -0
  170. package/dist/shell/window-manager.d.ts +124 -0
  171. package/dist/shell/window-manager.js +711 -0
  172. package/dist/shell/workbench-contract.d.ts +68 -0
  173. package/dist/shell/workbench-contract.js +1 -0
  174. package/dist/shell/workbench-directory.d.ts +332 -0
  175. package/dist/shell/workbench-directory.js +35 -0
  176. package/dist/shell/workbench-host-types.d.ts +96 -0
  177. package/dist/shell/workbench-host-types.js +1 -0
  178. package/dist/shell/workbench-host.d.ts +34 -0
  179. package/dist/shell/workbench-host.js +347 -0
  180. package/dist/shell/workbench-model.d.ts +192 -0
  181. package/dist/shell/workbench-model.js +53 -0
  182. package/dist/shell/workbench.d.ts +4 -0
  183. package/dist/shell/workbench.js +2 -0
  184. package/dist/source/fork.d.ts +27 -0
  185. package/dist/source/fork.js +46 -0
  186. package/dist/source/index.d.ts +10 -0
  187. package/dist/source/index.js +10 -0
  188. package/dist/source/seed-receipt.d.ts +53 -0
  189. package/dist/source/seed-receipt.js +18 -0
  190. package/dist/source/team-source.d.ts +32 -0
  191. package/dist/source/team-source.js +42 -0
  192. package/dist/source/template-push.d.ts +33 -0
  193. package/dist/source/template-push.js +223 -0
  194. package/dist/source/template-repository.d.ts +4 -0
  195. package/dist/source/template-repository.js +21 -0
  196. package/dist/source/template-seed.d.ts +68 -0
  197. package/dist/source/template-seed.js +80 -0
  198. package/dist/standalone/agent-directory.d.ts +4 -0
  199. package/dist/standalone/agent-directory.js +4 -0
  200. package/dist/standalone/agents.d.ts +220 -0
  201. package/dist/standalone/agents.js +795 -0
  202. package/dist/standalone/app-builds-binding.d.ts +36 -0
  203. package/dist/standalone/app-builds-binding.js +119 -0
  204. package/dist/standalone/app-set.d.ts +91 -0
  205. package/dist/standalone/app-set.js +395 -0
  206. package/dist/standalone/app-source.d.ts +41 -0
  207. package/dist/standalone/app-source.js +59 -0
  208. package/dist/standalone/builds.d.ts +18 -0
  209. package/dist/standalone/builds.js +101 -0
  210. package/dist/standalone/computer-egress.d.ts +37 -0
  211. package/dist/standalone/computer-egress.js +111 -0
  212. package/dist/standalone/computer.d.ts +122 -0
  213. package/dist/standalone/computer.js +714 -0
  214. package/dist/standalone/install-apps-cli.d.ts +2 -0
  215. package/dist/standalone/install-apps-cli.js +126 -0
  216. package/dist/standalone/instance.d.ts +35 -0
  217. package/dist/standalone/instance.js +74 -0
  218. package/dist/standalone/media.d.ts +76 -0
  219. package/dist/standalone/media.js +227 -0
  220. package/dist/standalone/preview.d.ts +51 -0
  221. package/dist/standalone/preview.js +167 -0
  222. package/dist/standalone/registry.d.ts +102 -0
  223. package/dist/standalone/registry.js +193 -0
  224. package/dist/standalone/tenancy.d.ts +42 -0
  225. package/dist/standalone/tenancy.js +241 -0
  226. package/dist/standalone/worker.d.ts +55 -0
  227. package/dist/standalone/worker.js +147 -0
  228. package/dist/standalone/workflow-authority-readers.d.ts +16 -0
  229. package/dist/standalone/workflow-authority-readers.js +35 -0
  230. package/dist/standalone/workflow-binding.d.ts +216 -0
  231. package/dist/standalone/workflow-binding.js +217 -0
  232. package/dist/standalone/workflow-events.d.ts +46 -0
  233. package/dist/standalone/workflow-events.js +39 -0
  234. package/dist/standalone/workflow-git-capture.d.ts +7 -0
  235. package/dist/standalone/workflow-git-capture.js +62 -0
  236. package/dist/standalone/workflow-host.d.ts +931 -0
  237. package/dist/standalone/workflow-host.js +751 -0
  238. package/dist/standalone/workflow-node-capture.d.ts +13 -0
  239. package/dist/standalone/workflow-node-capture.js +72 -0
  240. package/dist/standalone/workflow-root-authority.d.ts +27 -0
  241. package/dist/standalone/workflow-root-authority.js +87 -0
  242. package/dist/standalone/workflow-schedules.d.ts +52 -0
  243. package/dist/standalone/workflow-schedules.js +40 -0
  244. package/dist/standalone/workflow-trigger-host.d.ts +1694 -0
  245. package/dist/standalone/workflow-trigger-host.js +52 -0
  246. package/dist/standalone/workflows.d.ts +657 -0
  247. package/dist/standalone/workflows.js +554 -0
  248. package/dist/storage-frame.d.ts +64 -0
  249. package/dist/storage-frame.js +28 -0
  250. package/dist/tenancy/auth0.d.ts +64 -0
  251. package/dist/tenancy/auth0.js +198 -0
  252. package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
  253. package/dist/tenancy/cloudflare/rate-limit.js +19 -0
  254. package/dist/tenancy/cloudflare/registry.d.ts +50 -0
  255. package/dist/tenancy/cloudflare/registry.js +301 -0
  256. package/dist/tenancy/cloudflare.d.ts +2 -0
  257. package/dist/tenancy/cloudflare.js +2 -0
  258. package/dist/tenancy/cold-directory.d.ts +13 -0
  259. package/dist/tenancy/cold-directory.js +43 -0
  260. package/dist/tenancy/contracts.d.ts +195 -0
  261. package/dist/tenancy/contracts.js +1 -0
  262. package/dist/tenancy/index.d.ts +19 -0
  263. package/dist/tenancy/index.js +156 -0
  264. package/dist/tenancy/owner-key.d.ts +26 -0
  265. package/dist/tenancy/owner-key.js +63 -0
  266. package/dist/tenancy/oyster.d.ts +4 -0
  267. package/dist/tenancy/oyster.js +25 -0
  268. package/dist/tenancy/registry-record.d.ts +7 -0
  269. package/dist/tenancy/registry-record.js +44 -0
  270. package/dist/tenancy/registry.d.ts +27 -0
  271. package/dist/tenancy/registry.js +138 -0
  272. package/dist/tenancy/resolve.d.ts +7 -0
  273. package/dist/tenancy/resolve.js +64 -0
  274. package/dist/tenant.d.ts +81 -0
  275. package/dist/tenant.js +71 -0
  276. package/dist/testing/capabilities.d.ts +29 -0
  277. package/dist/testing/capabilities.js +130 -0
  278. package/dist/testing/catalog.d.ts +3 -0
  279. package/dist/testing/catalog.js +53 -0
  280. package/dist/testing/index.d.ts +10 -0
  281. package/dist/testing/index.js +10 -0
  282. package/dist/testing/installation.d.ts +11 -0
  283. package/dist/testing/installation.js +25 -0
  284. package/dist/testing/native-worker.d.ts +21 -0
  285. package/dist/testing/native-worker.js +106 -0
  286. package/dist/testing/worker.d.ts +18 -0
  287. package/dist/testing/worker.js +67 -0
  288. package/dist/testing/workers-runtime.d.ts +24 -0
  289. package/dist/testing/workers-runtime.js +33 -0
  290. package/dist/webhook-delivery.d.ts +14 -0
  291. package/dist/webhook-delivery.js +17 -0
  292. package/dist/workers-git.d.ts +285 -0
  293. package/dist/workers-git.js +1174 -0
  294. package/dist/workers.d.ts +352 -0
  295. package/dist/workers.js +868 -0
  296. package/docs/api/README.md +82 -0
  297. package/docs/api/agents.md +332 -0
  298. package/docs/api/app-storage.md +209 -0
  299. package/docs/api/apps.md +1375 -0
  300. package/docs/api/auth.md +63 -0
  301. package/docs/api/build-contract.md +1304 -0
  302. package/docs/api/build.md +1712 -0
  303. package/docs/api/cloudflare.md +421 -0
  304. package/docs/api/computer.md +203 -0
  305. package/docs/api/files.md +117 -0
  306. package/docs/api/git.md +470 -0
  307. package/docs/api/host.md +1328 -0
  308. package/docs/api/index.md +430 -0
  309. package/docs/api/invocation.md +738 -0
  310. package/docs/api/keys.md +175 -0
  311. package/docs/api/mcp.md +301 -0
  312. package/docs/api/media.md +40 -0
  313. package/docs/api/modules.md +50 -0
  314. package/docs/api/pack.md +141 -0
  315. package/docs/api/page/mount.md +298 -0
  316. package/docs/api/root-data.md +111 -0
  317. package/docs/api/shell/app-directory.md +599 -0
  318. package/docs/api/shell/command-host.md +224 -0
  319. package/docs/api/shell/commands.md +197 -0
  320. package/docs/api/shell/keys.md +183 -0
  321. package/docs/api/shell/shell-module.md +204 -0
  322. package/docs/api/shell/window-manager.md +267 -0
  323. package/docs/api/shell/workbench-directory.md +119 -0
  324. package/docs/api/shell/workbench.md +205 -0
  325. package/docs/api/shell.md +1915 -0
  326. package/docs/api/tenancy/auth0.md +110 -0
  327. package/docs/api/tenancy/cloudflare.md +221 -0
  328. package/docs/api/tenancy/cold-directory.md +46 -0
  329. package/docs/api/tenancy/owner-key.md +63 -0
  330. package/docs/api/tenancy/registry.md +77 -0
  331. package/docs/api/tenancy/resolve.md +69 -0
  332. package/docs/api/tenancy.md +430 -0
  333. package/docs/api/tenant.md +194 -0
  334. package/docs/api/workers-git.md +1646 -0
  335. package/docs/api/workers.md +1691 -0
  336. package/docs/site/agent-quickstart.mdx +346 -0
  337. package/examples/standalone/worker.ts +7 -0
  338. package/examples/standalone/wrangler.jsonc +82 -0
  339. package/package.json +300 -3
@@ -0,0 +1,751 @@
1
+ import * as S from "effect/Schema";
2
+ import { admitRunInput, accepts, catalogEvents, catalogMethods, catalogSchema, contentDigest, canonicalJson, parseProjection, nodeEventSchema, } from "@fungi.computer/braid";
3
+ import { deriveDeclaredOperations, approvedBindingGrants, grantsFor, } from "../apps/index.js";
4
+ import { pendingSchema } from "@fungi.computer/agar/backend-definition";
5
+ import { verifyRelease, } from "../host.js";
6
+ import { APP_INVOCATION_CEILING_MS, invocationAuthorityOf, invocationFacts, invocationFactsSchema, } from "../invocation.js";
7
+ import { AppInstanceError } from "../index.js";
8
+ import { appCapabilityIds } from "@fungi.computer/puffball";
9
+ import { createGraphWorkflowRuns, compileForMember, graphStartRequestSchema, preparationFromRun, planNodeCall, workflowRunContextSchema, } from "@fungi.computer/braid/runtime";
10
+ import { appMethodCallContextSchema } from "@fungi.computer/puffball/app";
11
+ import { appEventFactSchema, } from "@fungi.computer/puffball/app-event-facts";
12
+ import { keyAppAccess } from "../keys.js";
13
+ import { Validator } from "@cfworker/json-schema";
14
+ // Mirrors packages/oyster/src/cloudflare.ts's MAX_APP_REPLY_BYTES. That
15
+ // module imports "cloudflare:workers" at the top level, so it cannot be
16
+ // imported for its value outside a Workers runtime (e.g. this file's own
17
+ // node/vitest tests); apps/hub/src/app-workflow-broker.ts duplicates the
18
+ // same literal for the same reason.
19
+ const MAX_APP_REPLY_BYTES = 240 * 1024;
20
+ /**
21
+ * The key's own catalog view: an installation it cannot reach at all drops
22
+ * out, and a read-only installation keeps only its read methods. A node
23
+ * that reaches past the key then fails to compile with the engine's own
24
+ * "Installation is not available to you" or unknown-method issue -- never a
25
+ * live grant computed here.
26
+ */
27
+ function keyCatalogEntries(entries, key) {
28
+ const kept = [];
29
+ for (const entry of entries) {
30
+ const access = keyAppAccess(key, entry.installationId);
31
+ if (access === null)
32
+ continue;
33
+ if (access.level === "write") {
34
+ kept.push(entry);
35
+ continue;
36
+ }
37
+ const methods = Object.fromEntries(Object.entries(entry.methods).filter(([, method]) => method.effect === "read"));
38
+ kept.push({ ...entry, methods });
39
+ }
40
+ return kept;
41
+ }
42
+ const catalogProjectionSchema = S.Struct({
43
+ ...catalogSchema.value.fields,
44
+ displayName: S.String,
45
+ icon: S.optionalKey(S.String),
46
+ capabilities: S.optionalKey(S.Array(S.Literals(appCapabilityIds))),
47
+ });
48
+ const catalogRecordSchema = S.Struct({
49
+ release_key: S.String,
50
+ record: S.String,
51
+ });
52
+ const catalogProjectionCodec = S.fromJsonString(S.NullOr(catalogProjectionSchema));
53
+ /** Cached rows from another projection shape are recomputed, never read. */
54
+ const catalogProjectionVersion = "methods+events";
55
+ const releaseKey = (installation) => JSON.stringify([
56
+ catalogProjectionVersion,
57
+ installation.release.appId,
58
+ installation.release.releaseId,
59
+ installation.release.artifact.artifactRef,
60
+ installation.release.artifact.artifactDigest,
61
+ ]);
62
+ function summarizedMethod(method) {
63
+ const summary = {
64
+ description: method.description,
65
+ effect: method.effect,
66
+ approval: method.approval,
67
+ requires: method.requires,
68
+ digest: method.digest,
69
+ };
70
+ if (method.pending === true)
71
+ return { ...summary, pending: true };
72
+ return summary;
73
+ }
74
+ const summarizedEntry = (entry) => {
75
+ const summary = {
76
+ installationId: entry.installationId,
77
+ appId: entry.appId,
78
+ displayName: entry.displayName,
79
+ workflowPublishGranted: entry.workflowPublishGranted ?? false,
80
+ workflowRunGranted: entry.workflowRunGranted ?? false,
81
+ methods: Object.fromEntries(Object.entries(entry.methods).map(([name, method]) => [
82
+ name,
83
+ summarizedMethod(method),
84
+ ])),
85
+ };
86
+ if (entry.icon !== undefined)
87
+ summary.icon = entry.icon;
88
+ if (entry.capabilities !== undefined)
89
+ summary.capabilities = entry.capabilities;
90
+ if (entry.events !== undefined)
91
+ summary.events = Object.fromEntries(Object.entries(entry.events).map(([name, event]) => [
92
+ name,
93
+ { description: event.description, digest: event.digest },
94
+ ]));
95
+ return summary;
96
+ };
97
+ /** The standalone host's installation admission, above the portable engine. */
98
+ export function createStandaloneWorkflowHost(owner, host, options) {
99
+ const store = createGraphWorkflowRuns(owner);
100
+ // Only the host's attested invocation context is associated here. Lifecycle,
101
+ // events, reviews and replay are owned by the shared runtime store.
102
+ owner.sql.exec(`CREATE TABLE IF NOT EXISTS standalone_workflow_callers (
103
+ run_id TEXT PRIMARY KEY, caller TEXT NOT NULL
104
+ ) STRICT`);
105
+ const readCaller = (runId) => {
106
+ const row = owner.sql
107
+ .exec("SELECT caller FROM standalone_workflow_callers WHERE run_id=?", runId)
108
+ .toArray()[0];
109
+ if (row === undefined)
110
+ return null;
111
+ const saved = S.decodeUnknownSync(S.Struct({ caller: S.String }))(row);
112
+ return S.decodeUnknownSync(appMethodCallContextSchema)(JSON.parse(saved.caller));
113
+ };
114
+ const caller = (runId) => {
115
+ const saved = readCaller(runId);
116
+ if (saved === null)
117
+ throw new Error("Workflow caller is unavailable");
118
+ return saved;
119
+ };
120
+ // A dispatched Git step, keyed by its engine call identity: the only
121
+ // admission a Workflow Git pass mint accepts. Removed when the run ends.
122
+ owner.sql.exec(`CREATE TABLE IF NOT EXISTS standalone_workflow_git_nodes (
123
+ idempotency_key TEXT PRIMARY KEY, run_id TEXT NOT NULL, record TEXT NOT NULL
124
+ ) STRICT`);
125
+ const gitNodeCodec = S.fromJsonString(S.Struct({
126
+ teamId: S.String,
127
+ memberId: S.String,
128
+ installationId: S.String,
129
+ appId: S.String,
130
+ method: S.String,
131
+ methodDigest: S.String,
132
+ computerId: S.NullOr(S.String),
133
+ context: workflowRunContextSchema,
134
+ }));
135
+ const recordGitNode = (idempotencyKey, node) => {
136
+ const record = S.encodeSync(gitNodeCodec)({
137
+ ...node,
138
+ context: S.decodeUnknownSync(workflowRunContextSchema)(node.context),
139
+ });
140
+ owner.sql.exec("INSERT INTO standalone_workflow_git_nodes(idempotency_key,run_id,record) VALUES(?,?,?) ON CONFLICT(idempotency_key) DO UPDATE SET record=excluded.record", idempotencyKey, S.decodeUnknownSync(workflowRunContextSchema)(node.context).runId, record);
141
+ };
142
+ /** The dispatched Git step this call key names, while its run is active. */
143
+ const gitNode = (idempotencyKey) => {
144
+ const row = owner.sql
145
+ .exec("SELECT record FROM standalone_workflow_git_nodes WHERE idempotency_key=?", idempotencyKey)
146
+ .toArray()[0];
147
+ if (row === undefined)
148
+ return null;
149
+ const node = S.decodeUnknownSync(gitNodeCodec)(S.decodeUnknownSync(S.Struct({ record: S.String }))(row).record);
150
+ const run = store.read(node.context.runId);
151
+ return run?.teamId === node.teamId &&
152
+ store.outcome(node.context.runId) === "active"
153
+ ? node
154
+ : null;
155
+ };
156
+ owner.sql.exec(`CREATE TABLE IF NOT EXISTS standalone_workflow_catalog (
157
+ installation_id TEXT PRIMARY KEY, release_key TEXT NOT NULL, record TEXT NOT NULL
158
+ ) STRICT`);
159
+ const admitted = async (installationId) => {
160
+ const installation = await host.installations.get(installationId);
161
+ if (installation?.status !== "enabled")
162
+ throw new Error("Workflow installation is unavailable");
163
+ const verified = await verifyRelease(host.artifacts, installation.release, undefined, false);
164
+ return { installation, verified };
165
+ };
166
+ const publisherGranted = async (selected) => {
167
+ const declaration = selected.verified.declaration;
168
+ if (declaration === null)
169
+ return false;
170
+ const snapshot = await deriveDeclaredOperations(selected.installation.release, declaration, host.capabilities ?? []);
171
+ return (grantsFor(selected.installation, declaration, snapshot, "workflow.publish") !== null);
172
+ };
173
+ const runGranted = async (selected) => {
174
+ const declaration = selected.verified.declaration;
175
+ if (declaration === null)
176
+ return false;
177
+ const snapshot = await deriveDeclaredOperations(selected.installation.release, declaration, host.capabilities ?? []);
178
+ if (grantsFor(selected.installation, declaration, snapshot, "workflow.run") === null)
179
+ return false;
180
+ return approvedBindingGrants(selected.installation, declaration).some((binding) => binding.binding === "WORKFLOWS" &&
181
+ binding.operations.includes("runGraph"));
182
+ };
183
+ const checkLibraryOperation = (selected, operation) => {
184
+ if (!selected.verified.declaration?.capabilities.includes("workflow.run") ||
185
+ !selected.verified.declaration.backend?.bindings.some((binding) => binding.name === "WORKFLOWS" &&
186
+ binding.operations.includes(operation)))
187
+ throw new Error("Workflow operation is not granted");
188
+ };
189
+ const admitLibrary = async (installationId, operation) => {
190
+ const selected = await admitted(installationId);
191
+ checkLibraryOperation(selected, operation);
192
+ return selected;
193
+ };
194
+ const admitInvocation = async (raw, operation) => {
195
+ const facts = S.decodeUnknownSync(invocationFactsSchema)(raw, {
196
+ onExcessProperty: "error",
197
+ });
198
+ if (facts.subject.kind !== "call" ||
199
+ facts.selection.owner.kind !== "installation")
200
+ throw new AppInstanceError("revoked");
201
+ if (facts.deadlineAt <= Date.now())
202
+ throw new AppInstanceError("expired");
203
+ if (!facts.grants.some((grant) => grant.binding === "WORKFLOWS" && grant.operations.includes(operation)))
204
+ throw new AppInstanceError("revoked");
205
+ const libraryId = facts.selection.owner.id;
206
+ const selected = await admitLibrary(libraryId, operation);
207
+ const current = invocationAuthorityOf(selected.installation, selected.verified);
208
+ if (current.instanceId !== facts.instanceId ||
209
+ current.selection.appId !== facts.selection.appId ||
210
+ current.selection.releaseId !== facts.selection.releaseId ||
211
+ current.selection.authorityVersion !== facts.selection.authorityVersion ||
212
+ current.artifactDigest !== facts.artifactDigest)
213
+ throw new AppInstanceError("revoked");
214
+ if (facts.deadlineAt <= Date.now())
215
+ throw new AppInstanceError("expired");
216
+ return {
217
+ ...selected,
218
+ libraryId,
219
+ context: facts.subject.context,
220
+ installationAuthority: {
221
+ selection: current.selection,
222
+ artifactDigest: current.artifactDigest,
223
+ },
224
+ };
225
+ };
226
+ /** A trigger's live library pin; any missing grant is a typed revocation. */
227
+ const triggerAuthority = async (installationId) => {
228
+ const installation = await host.installations.get(installationId);
229
+ if (installation?.status !== "enabled")
230
+ throw new AppInstanceError("revoked");
231
+ const selected = await admitted(installationId);
232
+ if (!(await runGranted(selected)) ||
233
+ !selected.verified.declaration?.capabilities.includes("git.repository"))
234
+ throw new AppInstanceError("revoked");
235
+ const current = invocationAuthorityOf(selected.installation, selected.verified);
236
+ return {
237
+ installation: selected.installation,
238
+ pin: {
239
+ selection: current.selection,
240
+ artifactDigest: current.artifactDigest,
241
+ },
242
+ };
243
+ };
244
+ /** An emitted event fires triggers only as its live Release declares it. */
245
+ const admitEventFact = async (raw) => {
246
+ const fact = appEventFactSchema.parse(raw);
247
+ const { installation, verified } = await admitted(fact.installationId);
248
+ const declared = verified.api?.events?.[fact.type];
249
+ if (installation.release.appId !== fact.appId ||
250
+ installation.release.releaseId !== fact.releaseId ||
251
+ declared === undefined ||
252
+ fact.payload.type !== fact.type ||
253
+ !new Validator(declared.payload, "2020-12").validate(fact.payload).valid)
254
+ throw new AppInstanceError("revoked");
255
+ return fact;
256
+ };
257
+ // Immutable discovery data only. Admission and dispatch never read this copy.
258
+ const projection = async (installation, verifiedLibrary) => {
259
+ const key = releaseKey(installation);
260
+ const row = owner.sql
261
+ .exec("SELECT release_key,record FROM standalone_workflow_catalog WHERE installation_id=?", installation.installationId)
262
+ .toArray()[0];
263
+ if (row !== undefined) {
264
+ const record = S.decodeUnknownSync(catalogRecordSchema)(row);
265
+ if (record.release_key === key)
266
+ return S.decodeUnknownSync(catalogProjectionCodec)(record.record);
267
+ }
268
+ const verified = verifiedLibrary ??
269
+ (await verifyRelease(host.artifacts, installation.release, undefined, false));
270
+ let value = null;
271
+ if (verified.api !== null) {
272
+ value = {
273
+ ...(await catalogMethods(verified.api.methods)),
274
+ displayName: verified.declaration?.displayName ?? installation.installationId,
275
+ capabilities: verified.declaration?.capabilities ?? [],
276
+ };
277
+ if (verified.api.events !== undefined)
278
+ value = { ...value, ...(await catalogEvents(verified.api.events)) };
279
+ if (verified.declaration?.icon !== undefined)
280
+ value = { ...value, icon: verified.declaration.icon };
281
+ }
282
+ const record = JSON.stringify(value);
283
+ // A cache ceiling does not restrict the App API: larger projections still return.
284
+ if (new TextEncoder().encode(record).byteLength <= 512 * 1024)
285
+ owner.sql.exec("INSERT INTO standalone_workflow_catalog(installation_id,release_key,record) VALUES(?,?,?) ON CONFLICT(installation_id) DO UPDATE SET release_key=excluded.release_key,record=excluded.record", installation.installationId, key, record);
286
+ return value;
287
+ };
288
+ async function catalogForLibrary(library, scope) {
289
+ checkLibraryOperation(library, "catalog");
290
+ const wanted = scope !== undefined && "installationIds" in scope
291
+ ? new Set(scope.installationIds)
292
+ : null;
293
+ const installations = (await host.installations.list()).filter((installation) => installation.status === "enabled" &&
294
+ (wanted === null || wanted.has(installation.installationId)));
295
+ // This is the wire catalog's existing bound, not an installation limit.
296
+ if (installations.length > 200)
297
+ throw new Error("Workflow catalog is too large");
298
+ // Only a full sweep (no filter) ever enumerates every installation, so
299
+ // only a full sweep may prune cache rows for installations no longer
300
+ // present; a narrow filter must never touch other installations' rows.
301
+ if (wanted === null)
302
+ owner.sql.exec("DELETE FROM standalone_workflow_catalog WHERE installation_id NOT IN (SELECT value FROM json_each(?))", JSON.stringify(installations.map((installation) => installation.installationId)));
303
+ const apps = [];
304
+ for (let offset = 0; offset < installations.length; offset += 8) {
305
+ // Settle every sibling before returning, so no read outlives this method.
306
+ const results = await Promise.allSettled(installations.slice(offset, offset + 8).map(async (installation) => {
307
+ const value = await projection(installation, releaseKey(installation) === releaseKey(library.installation)
308
+ ? library.verified
309
+ : undefined);
310
+ if (value === null)
311
+ return null;
312
+ let workflowPublishGranted = false;
313
+ let workflowRunGranted = false;
314
+ if (value.capabilities?.includes("workflow.publish") ||
315
+ value.capabilities?.includes("workflow.run")) {
316
+ const current = await host.installations.get(installation.installationId);
317
+ if (current?.status !== "enabled" ||
318
+ releaseKey(current) !== releaseKey(installation))
319
+ throw new AppInstanceError("revoked");
320
+ const selected = releaseKey(current) === releaseKey(library.installation)
321
+ ? { installation: current, verified: library.verified }
322
+ : await admitted(current.installationId);
323
+ workflowPublishGranted = await publisherGranted(selected);
324
+ workflowRunGranted = await runGranted(selected);
325
+ const after = await host.installations.get(current.installationId);
326
+ if (after?.status !== "enabled" ||
327
+ after.authorityVersion !== current.authorityVersion ||
328
+ releaseKey(after) !== releaseKey(current))
329
+ throw new AppInstanceError("revoked");
330
+ }
331
+ return {
332
+ ...value,
333
+ workflowPublishGranted,
334
+ workflowRunGranted,
335
+ installationId: installation.installationId,
336
+ appId: installation.release.appId,
337
+ };
338
+ }));
339
+ for (const result of results) {
340
+ if (result.status === "rejected")
341
+ throw result.reason;
342
+ if (result.value !== null)
343
+ apps.push(result.value);
344
+ }
345
+ }
346
+ // Only the index ever crosses the App-reply wire boundary unbounded, so
347
+ // only it can refuse too_large; a typed refusal beats the generic
348
+ // App-reply ceiling's untyped throw when it still grows too large.
349
+ if (scope === undefined) {
350
+ const reply = { apps: apps.map(summarizedEntry) };
351
+ const bytes = new TextEncoder().encode(JSON.stringify(reply)).byteLength;
352
+ return bytes > MAX_APP_REPLY_BYTES
353
+ ? { error: "too_large" }
354
+ : reply;
355
+ }
356
+ return { apps };
357
+ }
358
+ const catalog = async (facts, input) => {
359
+ const library = await admitInvocation(facts, "catalog");
360
+ if (input?.installationId === undefined)
361
+ return catalogForLibrary(library);
362
+ const { apps } = await catalogForLibrary(library, {
363
+ installationIds: [input.installationId],
364
+ });
365
+ const [app] = apps;
366
+ if (app === undefined)
367
+ return { error: "not_found" };
368
+ // The single-installation detail is this method's own wire reply, so it
369
+ // alone (of the scoped fetches) still applies the ceiling.
370
+ const reply = { app };
371
+ const bytes = new TextEncoder().encode(JSON.stringify(reply)).byteLength;
372
+ return bytes > MAX_APP_REPLY_BYTES
373
+ ? { error: "too_large" }
374
+ : reply;
375
+ };
376
+ return {
377
+ store,
378
+ caller,
379
+ admitLibrary,
380
+ admitInvocation,
381
+ triggerAuthority,
382
+ admitEventFact,
383
+ gitNode,
384
+ /** A finished run's Git steps can no longer be named by a pass mint. */
385
+ forgetGitNodes(runId) {
386
+ owner.sql.exec("DELETE FROM standalone_workflow_git_nodes WHERE run_id=?", runId);
387
+ },
388
+ /** Trigger runs act as the owner who armed them on their pinned Computer. */
389
+ recordTriggerCaller(runId, memberId, computerId) {
390
+ const context = {
391
+ actor: { kind: "member", memberId },
392
+ computerId,
393
+ role: "owner",
394
+ };
395
+ owner.sql.exec("INSERT OR IGNORE INTO standalone_workflow_callers(run_id,caller) VALUES(?,?)", runId, JSON.stringify(context));
396
+ },
397
+ catalog,
398
+ // Host readers use this only after admitting the live member and library.
399
+ catalogForLibrary,
400
+ async prepare(facts, raw) {
401
+ const selected = await admitInvocation(facts, "runGraph");
402
+ const { libraryId, context } = selected;
403
+ const request = S.decodeUnknownSync(graphStartRequestSchema)(raw, {
404
+ onExcessProperty: "error",
405
+ });
406
+ const publication = request.published;
407
+ if (publication !== undefined) {
408
+ if (!(await publisherGranted(selected)))
409
+ return { error: "not_granted" };
410
+ if (publication.digest !==
411
+ (await contentDigest(S.decodeUnknownSync(S.Json)(request.definition))))
412
+ return { error: "invalid_publication" };
413
+ }
414
+ // A team-key caller never becomes a principal: it compiles against its
415
+ // own grants, strictly under the live member's access it borrows. The
416
+ // MCP gate has already required `apps.use` on this Workflows
417
+ // installation, because `runGraph` is a write.
418
+ const keyActor = context.actor.kind === "team-key" ? context.actor : null;
419
+ const key = keyActor === null
420
+ ? null
421
+ : ((await host.keys?.live(keyActor.keyId)) ?? null);
422
+ if (keyActor !== null && key === null)
423
+ return { error: "not_granted" };
424
+ // Compile reads only the Apps this request's own bindings name, never
425
+ // every installed App; a key then narrows those to its own grants.
426
+ const boundInstallationIds = [
427
+ ...new Set(Object.values(request.bindings).filter((value) => typeof value === "string")),
428
+ ];
429
+ const available = await catalogForLibrary(selected, {
430
+ installationIds: boundInstallationIds,
431
+ });
432
+ if ("error" in available)
433
+ return available;
434
+ const entries = key === null ? available.apps : keyCatalogEntries(available.apps, key);
435
+ const compiled = await compileForMember(entries, request);
436
+ if (compiled.kind === "invalid")
437
+ return compiled;
438
+ // This host has no human external-effect approval owner. A Git step needs
439
+ // a published run: its pass names the Workflow and the arming member.
440
+ // A key never gets a Git step either way: the key vocabulary has no
441
+ // Git grant yet.
442
+ const graphs = [
443
+ compiled.plan.definition,
444
+ ...Object.values(compiled.plan.definition.subgraphs ?? {}),
445
+ ];
446
+ if (graphs.some((graph) => graph.nodes.some((node) => {
447
+ if (node.kind !== "app")
448
+ return false;
449
+ const binding = compiled.plan.bindings[node.app];
450
+ return ((node.gitAccess !== undefined &&
451
+ (key !== null || publication === undefined)) ||
452
+ (binding !== undefined &&
453
+ "appId" in binding &&
454
+ compiled.plan.catalog[binding.appId]?.methods[node.method]
455
+ ?.effect === "external"));
456
+ })))
457
+ return { error: "not_granted" };
458
+ const admitted = admitRunInput(compiled.plan.definition, request.input);
459
+ if (admitted.kind === "invalid")
460
+ return { error: "invalid_input" };
461
+ const runId = "run_" +
462
+ (await contentDigest([libraryId, request.requestId])).slice(7, 47);
463
+ // An Agent runs with the access of the person who approved its App
464
+ // tools.
465
+ const subject = facts.subject.kind === "call" ? facts.subject : null;
466
+ const agent = context.actor.kind === "agent" &&
467
+ subject?.agentPolicy !== undefined &&
468
+ subject.sessionId !== undefined
469
+ ? {
470
+ kind: "agent",
471
+ agentId: context.actor.agentId,
472
+ sessionId: subject.sessionId,
473
+ grantorMemberId: subject.agentPolicy.grantorMemberId,
474
+ }
475
+ : null;
476
+ // A key's run always acts as its owner, with role owner, the same
477
+ // path an Agent's grantor already takes. The key never becomes a
478
+ // member or an Agent principal on its own request.
479
+ const keyInitiator = keyActor === null || key === null
480
+ ? null
481
+ : { kind: "key", keyId: keyActor.keyId, grantorMemberId: key.owner };
482
+ if (context.actor.kind !== "member" &&
483
+ agent === null &&
484
+ keyInitiator === null)
485
+ return { error: "not_granted" };
486
+ const actorId = agent !== null
487
+ ? agent.grantorMemberId
488
+ : keyInitiator !== null
489
+ ? keyInitiator.grantorMemberId
490
+ : context.actor.kind === "member"
491
+ ? context.actor.memberId
492
+ : undefined;
493
+ if (actorId === undefined)
494
+ return { error: "not_granted" };
495
+ // A key has no Agent Session, so it can never declare a delivery.
496
+ if (request.delivery !== undefined &&
497
+ (keyInitiator !== null ||
498
+ !(await options.admitDelivery(actorId, request.delivery))))
499
+ return { error: "not_granted" };
500
+ const preparation = agent !== null
501
+ ? {
502
+ initiatedBy: agent,
503
+ source: "agent",
504
+ libraryInstallationId: libraryId,
505
+ computerId: context.computerId,
506
+ plan: compiled.plan,
507
+ }
508
+ : keyInitiator !== null
509
+ ? {
510
+ initiatedBy: keyInitiator,
511
+ source: "key",
512
+ libraryInstallationId: libraryId,
513
+ computerId: context.computerId,
514
+ plan: compiled.plan,
515
+ }
516
+ : {
517
+ initiatedBy: { kind: "member", memberId: actorId },
518
+ source: "manual",
519
+ libraryInstallationId: libraryId,
520
+ computerId: context.computerId,
521
+ plan: compiled.plan,
522
+ };
523
+ const captured = await options.authority(libraryId).resolve(preparation);
524
+ if (captured.kind !== "admitted" || captured.actor.memberId !== actorId)
525
+ return { error: "not_granted" };
526
+ // A declared delivery wins; an Agent's own run reports to its Session.
527
+ const delivery = request.delivery ??
528
+ (agent === null
529
+ ? undefined
530
+ : { agentId: agent.agentId, sessionId: agent.sessionId });
531
+ const started = {
532
+ teamId: libraryId,
533
+ runId,
534
+ requestId: await contentDigest([libraryId, request.requestId]),
535
+ actorId,
536
+ computerId: context.computerId,
537
+ plan: compiled.plan,
538
+ ceiling: captured.ceiling,
539
+ input: admitted.input,
540
+ source: preparation.source,
541
+ initiatedBy: preparation.initiatedBy,
542
+ };
543
+ const record = delivery === undefined ? started : { ...started, delivery };
544
+ const current = await admitInvocation(facts, "runGraph");
545
+ if (publication !== undefined && !(await publisherGranted(current)))
546
+ return { error: "not_granted" };
547
+ const callContext = {
548
+ actor: captured.actor,
549
+ computerId: context.computerId,
550
+ };
551
+ // Standalone admits only the owner, so an Agent's or a key's grantor
552
+ // acts as owner.
553
+ if (agent !== null || keyInitiator !== null)
554
+ callContext.role = "owner";
555
+ else if (context.role !== undefined)
556
+ callContext.role = context.role;
557
+ const run = publication === undefined
558
+ ? record
559
+ : {
560
+ ...record,
561
+ publication: {
562
+ installationId: libraryId,
563
+ workflowId: publication.workflowId,
564
+ commit: publication.commit,
565
+ digest: publication.digest,
566
+ armedBy: captured.actor,
567
+ },
568
+ };
569
+ const kind = owner.transactionSync(() => {
570
+ const savedCaller = readCaller(runId);
571
+ if (savedCaller !== null &&
572
+ canonicalJson(S.decodeUnknownSync(S.Json)(savedCaller)) !==
573
+ canonicalJson(S.decodeUnknownSync(S.Json)(callContext)))
574
+ return "conflict";
575
+ // A replay from a different source or initiator fails here too: the
576
+ // store compares the whole stored record, source and initiator included.
577
+ const accepted = store.startCompiled(run);
578
+ if (accepted !== "conflict")
579
+ owner.sql.exec("INSERT OR IGNORE INTO standalone_workflow_callers(run_id,caller) VALUES(?,?)", runId, JSON.stringify(callContext));
580
+ return accepted;
581
+ });
582
+ return { kind, runId, planDigest: compiled.plan.planDigest };
583
+ },
584
+ async loadRun(libraryId, runId) {
585
+ await admitLibrary(libraryId, "runGraph");
586
+ const run = store.read(runId);
587
+ if (run?.teamId !== libraryId || store.outcome(runId) !== "active")
588
+ throw new Error("Workflow run is not active");
589
+ return { plan: run.plan, input: run.input };
590
+ },
591
+ /** Live member, Computer and library run grant, before every node kind. */
592
+ async beforeNode(raw) {
593
+ const event = S.decodeUnknownSync(nodeEventSchema)(raw, {
594
+ onExcessProperty: "error",
595
+ });
596
+ const run = store.read(event.runId);
597
+ if (run === null || store.outcome(event.runId) !== "active")
598
+ return { kind: "refused", code: "revoked" };
599
+ const savedCaller = caller(run.runId);
600
+ if (savedCaller.actor.kind !== "member")
601
+ return { kind: "refused", code: "revoked" };
602
+ const decision = await options
603
+ .authority(run.teamId)
604
+ .recheckRun(preparationFromRun(run, run.teamId));
605
+ if (decision.kind === "refused")
606
+ store.recordEvent({
607
+ ...event,
608
+ state: "failed",
609
+ refusal: decision.code,
610
+ errorMessage: decision.code.replaceAll("_", " "),
611
+ });
612
+ return decision;
613
+ },
614
+ async dispatch(call) {
615
+ const run = store.read(call.runId);
616
+ if (run?.teamId !== call.teamId || store.outcome(call.runId) !== "active")
617
+ return { kind: "refused", code: "revoked" };
618
+ await admitLibrary(run.teamId, "runGraph");
619
+ const resolved = planNodeCall(run, call);
620
+ if (resolved === null)
621
+ return { kind: "refused", code: "not_granted" };
622
+ const pinned = run.plan.catalog[resolved.appId]?.methods[resolved.method];
623
+ const { installation, verified } = await admitted(call.installationId);
624
+ if (verified.api === null ||
625
+ installation.release.appId !== resolved.appId)
626
+ return { kind: "refused", code: "revoked" };
627
+ const current = (await catalogMethods(verified.api.methods)).methods[resolved.method];
628
+ if (!pinned ||
629
+ current?.digest !== pinned.digest ||
630
+ pinned.effect === "external")
631
+ return { kind: "refused", code: "revoked" };
632
+ if (store.outcome(call.runId) !== "active")
633
+ return { kind: "refused", code: "revoked" };
634
+ const savedCaller = caller(run.runId);
635
+ if (savedCaller.actor.kind !== "member")
636
+ return { kind: "refused", code: "revoked" };
637
+ const live = options.authority(run.teamId);
638
+ await live.member(savedCaller.actor.memberId);
639
+ if (run.computerId !== null &&
640
+ !(await live.memberComputer(savedCaller.actor.memberId, run.computerId)))
641
+ return { kind: "refused", code: "revoked" };
642
+ const currentAuthority = invocationAuthorityOf(installation, verified);
643
+ const authority = {
644
+ ...currentAuthority,
645
+ // A binding this node's ceiling leaves no operation on is not granted.
646
+ grants: currentAuthority.grants
647
+ .map((grant) => ({
648
+ ...grant,
649
+ operations: grant.operations.filter((operation) => resolved.bindingCeiling.some((binding) => binding.name === grant.binding &&
650
+ binding.operations.includes(operation))),
651
+ }))
652
+ .filter((grant) => grant.operations.length > 0),
653
+ };
654
+ const deadlineAt = Date.now() + APP_INVOCATION_CEILING_MS;
655
+ const deadline = { deadlineAt, grantExpiresAt: deadlineAt };
656
+ const facts = run.publication === undefined
657
+ ? invocationFacts.fromCaller(authority, {
658
+ ...savedCaller,
659
+ idempotencyKey: call.idempotencyKey,
660
+ }, deadline)
661
+ : invocationFacts.fromWorkflowNode(authority, {
662
+ ...savedCaller,
663
+ workflow: {
664
+ workflowId: run.publication.workflowId,
665
+ runId: run.runId,
666
+ nodeId: resolved.nodeId,
667
+ armedBy: savedCaller.actor,
668
+ },
669
+ idempotencyKey: call.idempotencyKey,
670
+ }, deadline);
671
+ if (resolved.gitAccess !== undefined)
672
+ recordGitNode(call.idempotencyKey, {
673
+ teamId: run.teamId,
674
+ memberId: savedCaller.actor.memberId,
675
+ installationId: resolved.installationId,
676
+ appId: resolved.appId,
677
+ method: resolved.method,
678
+ methodDigest: resolved.methodDigest,
679
+ computerId: run.computerId,
680
+ context: resolved,
681
+ });
682
+ const result = await host.instances
683
+ .get(authority.instanceId)
684
+ .call(facts, verified.api, resolved.method, resolved.input);
685
+ if (store.outcome(call.runId) !== "active")
686
+ return { kind: "refused", code: "revoked" };
687
+ if (!result.ok) {
688
+ // InstanceResult's own producer vocabulary (packages/oyster) names
689
+ // every structural/authority rejection as an AppInstanceErrorCode;
690
+ // "failed" is its sole catch-all for an unclassified backend
691
+ // failure. Only a code that is actually a live decision about
692
+ // today's grant refuses the node as "revoked" (or, for a binding
693
+ // the Release never granted, "not_granted"). Every execution or
694
+ // availability code -- a deadline "expired" included -- throws, so
695
+ // the graph records a failed step and the engine's own retry
696
+ // applies, never a silent "revoked" that misreports a timeout or a
697
+ // transient unavailability as an authority decision.
698
+ switch (result.code) {
699
+ // A moved authority version is itself the live decision, not a
700
+ // retryable fault; same for an instance the owner state marks
701
+ // inactive.
702
+ case "revoked":
703
+ case "instance_owner_mismatch":
704
+ case "instance_authority_version_mismatch":
705
+ case "instance_release_mismatch":
706
+ case "release_no_longer_matches_instance":
707
+ case "installation_tenant_mismatch":
708
+ case "stale_authority":
709
+ case "instance_inactive":
710
+ return { kind: "refused", code: "revoked" };
711
+ case "binding_not_granted":
712
+ return { kind: "refused", code: "not_granted" };
713
+ case "failed":
714
+ throw new Error(result.causeClass === undefined
715
+ ? "App execution failed"
716
+ : `App execution failed (${result.causeClass})`);
717
+ case "expired":
718
+ case "binding_unavailable":
719
+ case "reentrant":
720
+ case "conflicting_owner_state":
721
+ case "invalid_owner_state":
722
+ case "invalid_invocation_facts":
723
+ case "instance_identity_mismatch":
724
+ case "instance_deleted":
725
+ case "release_app_mismatch":
726
+ case "state_schema_migration_unsupported":
727
+ throw new Error(`App execution failed (${result.code})`);
728
+ default: {
729
+ const exhaustive = result.code;
730
+ throw new Error(`App execution failed (${String(exhaustive)})`);
731
+ }
732
+ }
733
+ }
734
+ // The engine's closed refusal vocabulary names host decisions; an App's
735
+ // own domain refusal fails the node with its code.
736
+ if (result.value.kind === "refused")
737
+ throw new Error(`App method refused (${result.value.code})`);
738
+ const value = result.value.value;
739
+ const pending = pinned.pending === true ? pendingSchema.safeParse(value) : null;
740
+ if (pending?.success)
741
+ return pending.data;
742
+ if (!accepts(parseProjection(pinned.output), value))
743
+ return { kind: "refused", code: "invalid_output" };
744
+ return {
745
+ kind: "complete",
746
+ output: S.decodeUnknownSync(S.Json)(value),
747
+ costMilliunits: 0,
748
+ };
749
+ },
750
+ };
751
+ }