@fungi.computer/oyster 0.1.0 → 0.3.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 (292) hide show
  1. package/README.md +376 -72
  2. package/dist/agent-avatars.d.ts +103 -0
  3. package/dist/agent-avatars.js +66 -0
  4. package/dist/agents.d.ts +15 -4
  5. package/dist/agents.js +43 -4
  6. package/dist/app-builds.d.ts +81 -8
  7. package/dist/app-builds.js +210 -60
  8. package/dist/app-storage.d.ts +3 -3
  9. package/dist/approval-capability.d.ts +5 -0
  10. package/dist/approval-capability.js +46 -0
  11. package/dist/approval-home.d.ts +51 -0
  12. package/dist/approval-home.js +226 -0
  13. package/dist/apps/catalog.d.ts +335 -0
  14. package/dist/apps/catalog.js +46 -0
  15. package/dist/apps/gate.d.ts +26 -12
  16. package/dist/apps/gate.js +48 -22
  17. package/dist/apps/index.d.ts +3 -1
  18. package/dist/apps/index.js +3 -1
  19. package/dist/apps/installation.d.ts +147 -66
  20. package/dist/apps/installation.js +193 -86
  21. package/dist/apps/permissions.d.ts +247 -0
  22. package/dist/apps/permissions.js +255 -0
  23. package/dist/boot-capability-route.d.ts +15 -0
  24. package/dist/boot-capability-route.js +196 -0
  25. package/dist/boot-capability.d.ts +103 -0
  26. package/dist/boot-capability.js +141 -0
  27. package/dist/boot-construction.d.ts +12 -0
  28. package/dist/boot-construction.js +175 -0
  29. package/dist/boot-gate.d.ts +267 -0
  30. package/dist/boot-gate.js +236 -0
  31. package/dist/boot-host-script.d.ts +2 -0
  32. package/dist/boot-host-script.js +1 -0
  33. package/dist/boot-mount.d.ts +21 -0
  34. package/dist/boot-mount.js +65 -0
  35. package/dist/browser/boot.d.ts +73 -0
  36. package/dist/browser/boot.js +502 -0
  37. package/dist/browser/browser.d.ts +104 -0
  38. package/dist/browser/browser.js +374 -0
  39. package/dist/browser/capability-source.d.ts +76 -0
  40. package/dist/browser/capability-source.js +338 -0
  41. package/dist/browser/cdp-client.d.ts +56 -0
  42. package/dist/browser/cdp-client.js +184 -0
  43. package/dist/browser/errors.d.ts +79 -0
  44. package/dist/browser/errors.js +32 -0
  45. package/dist/browser/index.d.ts +16 -0
  46. package/dist/browser/index.js +14 -0
  47. package/dist/browser/lease-registry.d.ts +61 -0
  48. package/dist/browser/lease-registry.js +101 -0
  49. package/dist/browser/operations.d.ts +225 -0
  50. package/dist/browser/operations.js +137 -0
  51. package/dist/browser/page.d.ts +25 -0
  52. package/dist/browser/page.js +172 -0
  53. package/dist/browser/quick-reads.d.ts +29 -0
  54. package/dist/browser/quick-reads.js +160 -0
  55. package/dist/browser/sessions.d.ts +52 -0
  56. package/dist/browser/sessions.js +118 -0
  57. package/dist/browser/url-policy.d.ts +18 -0
  58. package/dist/browser/url-policy.js +58 -0
  59. package/dist/build/app-imports.d.ts +12 -0
  60. package/dist/build/app-imports.js +105 -0
  61. package/dist/build/build-policy.d.ts +5 -3
  62. package/dist/build/build-policy.js +5 -3
  63. package/dist/build/candidate-builder.d.ts +1 -1
  64. package/dist/build/candidate-cli.mjs +8428 -337
  65. package/dist/build/local-build.d.ts +2 -0
  66. package/dist/build/local-build.js +28 -6
  67. package/dist/build/snapshot-package-store.d.ts +1 -1
  68. package/dist/build/snapshot-package-store.js +7 -2
  69. package/dist/build/workspace-build.d.ts +92 -5
  70. package/dist/build/workspace-build.js +93 -19
  71. package/dist/build-egress.js +4 -4
  72. package/dist/build.d.ts +1 -1
  73. package/dist/build.js +1 -1
  74. package/dist/catalog-frame.js +1 -1
  75. package/dist/cloudflare.d.ts +13 -3
  76. package/dist/cloudflare.js +106 -8
  77. package/dist/frame-binding-wire-script.js +1 -1
  78. package/dist/frame-binding-wire.d.ts +1 -1
  79. package/dist/frame-binding-wire.js +5 -3
  80. package/dist/git-auth.d.ts +0 -2
  81. package/dist/git-auth.js +0 -2
  82. package/dist/git-journal.js +4 -4
  83. package/dist/git.d.ts +0 -1
  84. package/dist/git.js +0 -2
  85. package/dist/host.d.ts +39 -13
  86. package/dist/host.js +268 -142
  87. package/dist/index.d.ts +26 -15
  88. package/dist/index.js +40 -16
  89. package/dist/install-release-cli.d.ts +2 -0
  90. package/dist/install-release-cli.js +46 -0
  91. package/dist/installation-client.d.ts +43 -0
  92. package/dist/installation-client.js +178 -0
  93. package/dist/installation-execution.d.ts +11 -0
  94. package/dist/installation-execution.js +184 -0
  95. package/dist/installation-operations.d.ts +1437 -0
  96. package/dist/installation-operations.js +664 -0
  97. package/dist/installation-store-generation.d.ts +3 -0
  98. package/dist/installation-store-generation.js +16 -0
  99. package/dist/instance-id.d.ts +10 -0
  100. package/dist/instance-id.js +10 -0
  101. package/dist/instance-refusal.d.ts +11 -0
  102. package/dist/instance-refusal.js +21 -0
  103. package/dist/internal/delivery.js +4 -2
  104. package/dist/internal/host-gate.js +7 -1
  105. package/dist/internal/registry-installations.js +4 -4
  106. package/dist/keys.d.ts +5 -1
  107. package/dist/keys.js +10 -2
  108. package/dist/machine-link/authority.d.ts +89 -0
  109. package/dist/machine-link/authority.js +37 -0
  110. package/dist/machine-link/client.d.ts +66 -0
  111. package/dist/machine-link/client.js +94 -0
  112. package/dist/machine-link/delivery.d.ts +18 -0
  113. package/dist/machine-link/delivery.js +55 -0
  114. package/dist/machine-link/enrollment.d.ts +111 -0
  115. package/dist/machine-link/enrollment.js +286 -0
  116. package/dist/machine-link/fences.d.ts +37 -0
  117. package/dist/machine-link/fences.js +166 -0
  118. package/dist/machine-link/home.d.ts +20 -0
  119. package/dist/machine-link/home.js +24 -0
  120. package/dist/machine-link/host.d.ts +43 -0
  121. package/dist/machine-link/host.js +189 -0
  122. package/dist/machine-link/index.d.ts +15 -0
  123. package/dist/machine-link/index.js +15 -0
  124. package/dist/machine-link/link.d.ts +48 -0
  125. package/dist/machine-link/link.js +223 -0
  126. package/dist/machine-link/presence.d.ts +10 -0
  127. package/dist/machine-link/presence.js +25 -0
  128. package/dist/machine-link/workers.d.ts +6 -0
  129. package/dist/machine-link/workers.js +6 -0
  130. package/dist/mcp-owner.d.ts +2 -2
  131. package/dist/mcp-owner.js +41 -33
  132. package/dist/mcp.d.ts +15 -3
  133. package/dist/mcp.js +146 -25
  134. package/dist/pack.d.ts +14 -3
  135. package/dist/pack.js +44 -19
  136. package/dist/page/app-access.d.ts +7 -0
  137. package/dist/page/app-access.js +75 -0
  138. package/dist/page/boot-host.js +57 -0
  139. package/dist/page/capabilities.d.ts +3 -3
  140. package/dist/page/capabilities.js +16 -12
  141. package/dist/page/computer-handles.d.ts +3 -3
  142. package/dist/page/computer-target.d.ts +3 -0
  143. package/dist/page/computer-target.js +9 -0
  144. package/dist/page/mount.d.ts +23 -7
  145. package/dist/page/mount.js +152 -16
  146. package/dist/page-assets.d.ts +22 -0
  147. package/dist/page-assets.js +28 -0
  148. package/dist/page-script.d.ts +3 -0
  149. package/dist/page-script.js +5 -4
  150. package/dist/release.d.ts +13 -6
  151. package/dist/release.js +10 -7
  152. package/dist/root-data.d.ts +16 -6
  153. package/dist/root-data.js +2 -2
  154. package/dist/source/index.d.ts +1 -1
  155. package/dist/source/team-source.d.ts +20 -8
  156. package/dist/source/team-source.js +39 -10
  157. package/dist/source/template-seed.d.ts +2 -2
  158. package/dist/standalone/agent-mail.d.ts +24 -0
  159. package/dist/standalone/agent-mail.js +62 -0
  160. package/dist/standalone/agent-store.d.ts +9 -0
  161. package/dist/standalone/agent-store.js +30 -0
  162. package/dist/standalone/agents.d.ts +78 -12
  163. package/dist/standalone/agents.js +470 -59
  164. package/dist/standalone/app-builds-binding.d.ts +20 -2
  165. package/dist/standalone/app-builds-binding.js +151 -27
  166. package/dist/standalone/app-set.d.ts +5 -2
  167. package/dist/standalone/app-set.js +174 -143
  168. package/dist/standalone/browser.d.ts +11 -0
  169. package/dist/standalone/browser.js +72 -0
  170. package/dist/standalone/builds.d.ts +13 -4
  171. package/dist/standalone/builds.js +28 -44
  172. package/dist/standalone/computer-egress.d.ts +2 -0
  173. package/dist/standalone/computer-egress.js +13 -4
  174. package/dist/standalone/computer-fleet.d.ts +43 -0
  175. package/dist/standalone/computer-fleet.js +200 -0
  176. package/dist/standalone/computer.d.ts +44 -9
  177. package/dist/standalone/computer.js +132 -25
  178. package/dist/standalone/install-apps-cli.js +43 -6
  179. package/dist/standalone/instance.d.ts +2 -2
  180. package/dist/standalone/instance.js +8 -11
  181. package/dist/standalone/machine-link.d.ts +354 -0
  182. package/dist/standalone/machine-link.js +368 -0
  183. package/dist/standalone/media.d.ts +2 -2
  184. package/dist/standalone/media.js +19 -16
  185. package/dist/standalone/preview.d.ts +2 -2
  186. package/dist/standalone/preview.js +39 -12
  187. package/dist/standalone/registry.d.ts +149 -9
  188. package/dist/standalone/registry.js +299 -8
  189. package/dist/standalone/worker.d.ts +19 -5
  190. package/dist/standalone/worker.js +90 -54
  191. package/dist/standalone/workflow-binding.d.ts +28 -32
  192. package/dist/standalone/workflow-binding.js +20 -20
  193. package/dist/standalone/workflow-control.d.ts +4 -0
  194. package/dist/standalone/workflow-control.js +40 -0
  195. package/dist/standalone/workflow-events.d.ts +2 -2
  196. package/dist/standalone/workflow-events.js +9 -10
  197. package/dist/standalone/workflow-git-capture.js +4 -3
  198. package/dist/standalone/workflow-host.d.ts +177 -152
  199. package/dist/standalone/workflow-host.js +137 -30
  200. package/dist/standalone/workflow-node-capture.js +11 -4
  201. package/dist/standalone/workflow-schedules.d.ts +5 -2
  202. package/dist/standalone/workflow-schedules.js +9 -10
  203. package/dist/standalone/workflow-trigger-host.d.ts +88 -6
  204. package/dist/standalone/workflow-trigger-host.js +2 -2
  205. package/dist/standalone/workflow-webhooks.d.ts +22 -0
  206. package/dist/standalone/workflow-webhooks.js +39 -0
  207. package/dist/standalone/workflows.d.ts +203 -50
  208. package/dist/standalone/workflows.js +198 -39
  209. package/dist/storage-frame.d.ts +6 -6
  210. package/dist/testing/capabilities.js +14 -3
  211. package/dist/testing/installation.js +26 -2
  212. package/dist/testing/worker.js +1 -1
  213. package/dist/testing/workers-runtime.d.ts +8 -2
  214. package/dist/testing/workers-runtime.js +20 -2
  215. package/dist/webhook-abuse.d.ts +25 -0
  216. package/dist/webhook-abuse.js +54 -0
  217. package/dist/webhook-custody.d.ts +144 -0
  218. package/dist/webhook-custody.js +222 -0
  219. package/dist/webhook-ingress.d.ts +36 -0
  220. package/dist/webhook-ingress.js +146 -0
  221. package/dist/webhooks.d.ts +111 -0
  222. package/dist/webhooks.js +189 -0
  223. package/dist/workers-git.d.ts +12 -17
  224. package/dist/workers-git.js +60 -55
  225. package/dist/workers.d.ts +224 -13
  226. package/dist/workers.js +351 -51
  227. package/dist/workflow-agent-authority.d.ts +8 -0
  228. package/dist/workflow-agent-authority.js +42 -0
  229. package/dist/workflows.d.ts +66 -0
  230. package/dist/workflows.js +97 -0
  231. package/docs/api/README.md +63 -37
  232. package/docs/api/agents.md +17 -0
  233. package/docs/api/app-storage.md +1 -1
  234. package/docs/api/apps.md +1394 -543
  235. package/docs/api/browser/boot.md +132 -0
  236. package/docs/api/browser.md +1440 -0
  237. package/docs/api/build-contract.md +38 -25
  238. package/docs/api/build.md +263 -84
  239. package/docs/api/cloudflare.md +93 -7
  240. package/docs/api/git.md +3 -13
  241. package/docs/api/host.md +944 -118
  242. package/docs/api/index.md +320 -18
  243. package/docs/api/keys.md +24 -1
  244. package/docs/api/mcp.md +59 -12
  245. package/docs/api/modules.md +33 -40
  246. package/docs/api/pack.md +57 -8
  247. package/docs/api/page/mount.md +92 -4
  248. package/docs/api/workers-git.md +103 -24
  249. package/docs/api/workers.md +1581 -244
  250. package/docs/site/agent-quickstart.mdx +28 -14
  251. package/examples/standalone/worker.ts +3 -1
  252. package/examples/standalone/wrangler.jsonc +2 -0
  253. package/package.json +73 -69
  254. package/dist/installation-consent.d.ts +0 -79
  255. package/dist/installation-consent.js +0 -82
  256. package/dist/shell/app-directory.d.ts +0 -542
  257. package/dist/shell/app-directory.js +0 -199
  258. package/dist/shell/browser-actions.d.ts +0 -7
  259. package/dist/shell/browser-actions.js +0 -56
  260. package/dist/shell/command-host.d.ts +0 -112
  261. package/dist/shell/command-host.js +0 -502
  262. package/dist/shell/commands.d.ts +0 -25
  263. package/dist/shell/commands.js +0 -78
  264. package/dist/shell/index.d.ts +0 -8
  265. package/dist/shell/index.js +0 -8
  266. package/dist/shell/keys.d.ts +0 -41
  267. package/dist/shell/keys.js +0 -134
  268. package/dist/shell/shell-module.d.ts +0 -55
  269. package/dist/shell/shell-module.js +0 -86
  270. package/dist/shell/window-manager.d.ts +0 -124
  271. package/dist/shell/window-manager.js +0 -711
  272. package/dist/shell/workbench-contract.d.ts +0 -68
  273. package/dist/shell/workbench-directory.d.ts +0 -332
  274. package/dist/shell/workbench-directory.js +0 -35
  275. package/dist/shell/workbench-host-types.d.ts +0 -96
  276. package/dist/shell/workbench-host-types.js +0 -1
  277. package/dist/shell/workbench-host.d.ts +0 -34
  278. package/dist/shell/workbench-host.js +0 -347
  279. package/dist/shell/workbench-model.d.ts +0 -192
  280. package/dist/shell/workbench-model.js +0 -53
  281. package/dist/shell/workbench.d.ts +0 -4
  282. package/dist/shell/workbench.js +0 -2
  283. package/docs/api/shell/app-directory.md +0 -599
  284. package/docs/api/shell/command-host.md +0 -224
  285. package/docs/api/shell/commands.md +0 -197
  286. package/docs/api/shell/keys.md +0 -183
  287. package/docs/api/shell/shell-module.md +0 -204
  288. package/docs/api/shell/window-manager.md +0 -267
  289. package/docs/api/shell/workbench-directory.md +0 -119
  290. package/docs/api/shell/workbench.md +0 -205
  291. package/docs/api/shell.md +0 -1915
  292. /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
@@ -0,0 +1,189 @@
1
+ import { defineIdFamily } from "@fungi.computer/cairn/id";
2
+ import { z } from "zod";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Result from "effect/Result";
5
+ import { armedWorkflowTriggerSchema, workflowTriggerTargetSchema, } from "@fungi.computer/braid/runtime";
6
+ import { workflowWebhookCustody, workflowWebhookVerifierSchema, } from "./webhook-custody.js";
7
+ import { workflowWebhookIngress, } from "./webhook-ingress.js";
8
+ import { workflowWebhookAbuse } from "./webhook-abuse.js";
9
+ const ids = defineIdFamily({
10
+ family: "workflow-webhooks",
11
+ version: 1,
12
+ kinds: { binding: { prefix: "workflow_hook" } },
13
+ });
14
+ const cargoSchema = z.strictObject({
15
+ binding: armedWorkflowTriggerSchema,
16
+ body: z.json(),
17
+ headers: z.partialRecord(z.enum(["content-type", "x-github-event"]), z.string().max(1024)),
18
+ });
19
+ const bindingId = z.string().min(1).max(256);
20
+ export const workflowWebhookCommandSchema = z.discriminatedUnion("operation", [
21
+ z.strictObject({
22
+ operation: z.literal("create"),
23
+ target: workflowTriggerTargetSchema,
24
+ computerId: bindingId,
25
+ verifier: workflowWebhookVerifierSchema,
26
+ }),
27
+ z.strictObject({
28
+ operation: z.enum(["rotate", "retirePrevious", "revoke"]),
29
+ bindingId,
30
+ }),
31
+ z.strictObject({ operation: z.literal("list"), after: bindingId.optional() }),
32
+ ]);
33
+ /**
34
+ * Dispatch the hosts' decoded closed control contract. Hosts decode before
35
+ * resolving installation dependencies; only they supply the current pin and
36
+ * their captured target.
37
+ */
38
+ export async function workflowWebhookControlResponse(webhooks, principal, command, authorize) {
39
+ if (command.operation === "create") {
40
+ const { target, authority } = await authorize(command.target, command.computerId);
41
+ return Response.json(await webhooks.create(principal, target, command.computerId, authority, command.verifier), { headers: { "cache-control": "no-store" } });
42
+ }
43
+ const result = command.operation === "list"
44
+ ? await webhooks.list(principal, command.after)
45
+ : await webhooks[command.operation](principal, command.bindingId);
46
+ return Response.json(result, { headers: { "cache-control": "no-store" } });
47
+ }
48
+ /**
49
+ * Host-owned composition: custody verifies, Braid reserves/maps/starts, and the
50
+ * host's ordinary idempotent launch ensures its recorded engine instance.
51
+ * No separate intake state machine or run/settlement store exists here.
52
+ */
53
+ export function createWorkflowWebhooks(options) {
54
+ const custody = workflowWebhookCustody(options);
55
+ const abuse = workflowWebhookAbuse({
56
+ ...options.abuse,
57
+ scope: options.scope,
58
+ });
59
+ let recovering = false;
60
+ const nextWake = () => options.runs.nextWebhookWake(options.scope);
61
+ const receive = workflowWebhookIngress({
62
+ limits: options.limits,
63
+ hostLease: () => abuse.hostLease(),
64
+ reportReadFailure: () => {
65
+ options.reportFailure();
66
+ },
67
+ reportOwnerFailure: () => {
68
+ options.reportFailure();
69
+ },
70
+ async deliver(delivery) {
71
+ if (!(await abuse.bindingAdmission(delivery.bindingId)))
72
+ return { status: "refused" };
73
+ const verified = custody.verification.verify(delivery);
74
+ if (verified.status !== "ok")
75
+ return { status: "refused" };
76
+ const body = z
77
+ .json()
78
+ .parse(JSON.parse(new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(delivery.body)));
79
+ const headers = Object.fromEntries(["content-type", "x-github-event"].flatMap((name) => {
80
+ const value = delivery.headers.get(name);
81
+ return value === null ? [] : [[name, value]];
82
+ }));
83
+ const cargo = cargoSchema.parse({
84
+ binding: verified.binding,
85
+ body,
86
+ headers,
87
+ });
88
+ const reserved = options.transactionSync(() => {
89
+ if (!custody.verification.isActive(delivery.bindingId))
90
+ return { kind: "refused" };
91
+ return options.runs.reserveWebhookDelivery({
92
+ scope: {
93
+ installationId: cargo.binding.installationId,
94
+ workflowId: cargo.binding.workflowId,
95
+ commit: cargo.binding.commit,
96
+ triggerId: cargo.binding.triggerId,
97
+ },
98
+ delivery: {
99
+ ...options.scope,
100
+ bindingId: delivery.bindingId,
101
+ eventId: verified.eventId,
102
+ },
103
+ cargo: z.json().parse(cargo),
104
+ limits: abuse.concurrency,
105
+ });
106
+ });
107
+ if (reserved.kind === "refused")
108
+ return { status: "refused" };
109
+ const accepted = {
110
+ status: "accepted",
111
+ deliveryId: reserved.reservationId,
112
+ };
113
+ // Admission already committed. A lost wake acknowledgement cannot undo
114
+ // it or prove refusal; the existing recovery owner retains this identity.
115
+ try {
116
+ await options.wake();
117
+ }
118
+ catch {
119
+ options.reportFailure();
120
+ return accepted;
121
+ }
122
+ return accepted;
123
+ },
124
+ });
125
+ return {
126
+ receive,
127
+ nextWake,
128
+ async create(principal, target, computerId, authority, verifier) {
129
+ const creator = await options.owner(principal);
130
+ if (creator === null)
131
+ return { kind: "refused" };
132
+ const armed = await options.triggers.arm({ armedBy: creator, armedVia: null }, target, computerId, authority);
133
+ if (armed.kind !== "armed")
134
+ return armed;
135
+ if (armed.trigger.on !== "webhook")
136
+ return { kind: "refused" };
137
+ return custody.create(principal, ids.binding.generate(), verifier, armed.binding);
138
+ },
139
+ rotate: (principal, bindingId) => custody.rotate(principal, bindingId),
140
+ retirePrevious: (principal, bindingId) => custody.retirePrevious(principal, bindingId),
141
+ revoke: (principal, bindingId) => custody.revoke(principal, bindingId),
142
+ list: (principal, after = null) => custody.list(principal, after),
143
+ async recover() {
144
+ if (recovering)
145
+ return;
146
+ recovering = true;
147
+ try {
148
+ for (const row of options.runs.pendingWebhookDeliveries(options.scope)) {
149
+ const outcome = await Effect.runPromise(Effect.result(Effect.tryPromise(async () => {
150
+ if (row.kind === "launch") {
151
+ // Durable admission linearized before revocation; finish ensuring
152
+ // THAT existing run, never admit a fresh run from expired cargo.
153
+ const run = options.runs.graph.read(row.runId);
154
+ if (run === null)
155
+ throw new Error("Reserved Workflow run is missing");
156
+ await options.launch({
157
+ runId: row.runId,
158
+ planDigest: run.plan.planDigest,
159
+ });
160
+ }
161
+ else {
162
+ const cargo = cargoSchema.parse(row.cargo);
163
+ const active = () => custody.verification.isActive(row.delivery.bindingId);
164
+ if (active())
165
+ await options.triggers.fire(cargo.binding, row.reservationId, {
166
+ body: cargo.body,
167
+ headers: cargo.headers,
168
+ eventId: row.delivery.eventId,
169
+ }, {
170
+ admitBinding: active,
171
+ reservationId: row.reservationId,
172
+ });
173
+ }
174
+ options.runs.completeWebhookDelivery(row.reservationId);
175
+ })));
176
+ if (Result.isFailure(outcome)) {
177
+ // Leave the SAME reservation pending; do not expose error text or
178
+ // mint a replacement request after a lost launch response.
179
+ options.reportFailure();
180
+ }
181
+ }
182
+ }
183
+ finally {
184
+ recovering = false;
185
+ await options.wake();
186
+ }
187
+ },
188
+ };
189
+ }
@@ -16,7 +16,7 @@ import { type OysterAgents } from "./agents.js";
16
16
  import { type ArtifactsNamespace } from "@fungi.computer/burrow";
17
17
  import { type HostEventSource, type HostRecipientEvent, type RecipientDelivery } from "./event-recipients.js";
18
18
  export type { HostEventSource, HostRecipientEvent, RecipientDelivery, } from "./event-recipients.js";
19
- import { type OysterCapability, type OysterEnv, type OysterGitHost, type OysterHost } from "./host.js";
19
+ import { type OysterAuth, type OysterCapability, type OysterEnv, type OysterGitHost, type OysterHost } from "./host.js";
20
20
  import type { GitWriter } from "@fungi.computer/agar/git";
21
21
  import { OysterInstance, type OysterBindingContext, type OysterBindingRow, type OysterBindingTable, type OysterWorkspace, type OysterWorkerEnv, type OysterInvocationHost } from "./workers.js";
22
22
  /** The GIT binding plus the credential only the host's git door may use. */
@@ -166,16 +166,6 @@ declare const OysterBinding_base: abstract new (ctx: ExecutionContext, env: Oyst
166
166
  /** A git Worker's one guest binding entrypoint, over `gitBindings`. */
167
167
  export declare class OysterBinding extends OysterBinding_base {
168
168
  }
169
- /** One listed or issued key's owner-facing summary; never its secret or owner id. */
170
- export type OysterKeySummary = Readonly<{
171
- keyId: string;
172
- name: string;
173
- grants: readonly Grant<OysterKeyCapability>[];
174
- expiresAt: number | null;
175
- createdAt: number;
176
- lastUsedAt: number | null;
177
- revokedAt: number | null;
178
- }>;
179
169
  /** Owner routes for keys and webhooks; `createOyster` mounts them owner-only. */
180
170
  export declare function gitAdminRoutes(env: OysterGitEnv, scope: TenantScope): Hono<OysterEnv>;
181
171
  /** A standalone Oyster Worker that also hosts git Apps. */
@@ -193,7 +183,7 @@ export type OysterGitWorkerEnv = OysterWorkerEnv & OysterGitEnv & Readonly<{
193
183
  export declare abstract class OysterGitInstance<Env extends OysterGitWorkerEnv = OysterGitWorkerEnv> extends OysterInstance<Env> {
194
184
  protected readonly bindings: OysterBindingTable<Env>;
195
185
  /** The deployable root's host for one pinned tenant: `root.hostFor(this.env, scope)`. */
196
- protected abstract hostFor(scope: TenantScope): OysterHost;
186
+ protected abstract hostFor(scope: TenantScope): Promise<OysterHost>;
197
187
  }
198
188
  /**
199
189
  * One deployable Git root's options. Every door (HTTP, MCP, Agents, Workflows,
@@ -201,6 +191,8 @@ export declare abstract class OysterGitInstance<Env extends OysterGitWorkerEnv =
201
191
  * `oysterGitRoot(options).hostFor`.
202
192
  */
203
193
  export type OysterGitWorkerOptions<Env extends OysterGitWorkerEnv> = Readonly<{
194
+ /** Workflow controls supplied by the host's existing run owner. */
195
+ workflows?: (env: Env, host: OysterGitWorkerHost) => NonNullable<OysterHost["workflows"]>;
204
196
  /** Resolve one host-selected tenant before authentication; unknown origins refuse.
205
197
  * A single-owner root passes `ownerOrKeyTenancy`. */
206
198
  tenancy: (env: Env) => OysterTenancy;
@@ -217,11 +209,14 @@ export type OysterGitWorkerOptions<Env extends OysterGitWorkerEnv> = Readonly<{
217
209
  * workspace, but whether that key's owner is still this tenant's live
218
210
  * owner is this one port's job, never reimplemented against `env.KEYS`.
219
211
  */
220
- workspace?: (env: Env, keysLive: OysterKeysLivePort) => OysterWorkspace;
212
+ workspace?: (env: Env, keysLive: OysterKeysLivePort, scope: TenantScope, request?: Readonly<{
213
+ request: Request;
214
+ auth: OysterAuth;
215
+ }>) => OysterWorkspace | Promise<OysterWorkspace>;
221
216
  /** Additional capabilities share the exact workspace selected for the base host. */
222
217
  capabilities?: (env: Env, workspace: OysterWorkspace, keysLive: OysterKeysLivePort) => readonly OysterCapability[];
223
- /** Admit a host-minted Workflow Git pass for one repository and scope;
224
- * omit when this host mints no Workflow Git passes. */
218
+ /** Admit a presented Workflow Git credential for one repository and scope;
219
+ * omit when this host issues no Workflow Git credentials. */
225
220
  workflowGitPass?: (env: Env, scope: TenantScope, pass: string, target: GitDoorTarget) => Promise<Extract<GitWriter, {
226
221
  kind: "actor";
227
222
  }> | null>;
@@ -249,7 +244,7 @@ export type OysterGitRoot<Env extends OysterGitWorkerEnv> = Readonly<{
249
244
  /** Select a request's tenant once; `hostFor` reuses that admission for the same request. */
250
245
  admit(env: Env, request: Request): Promise<ResolvedTenant | null>;
251
246
  /** One pinned tenant's host. HTTP, MCP, Agents, Workflows and background wakes all reach it here. */
252
- hostFor(env: Env, scope: TenantScope): OysterGitWorkerHost;
247
+ hostFor(env: Env, scope: TenantScope, request?: Request): Promise<OysterGitWorkerHost>;
253
248
  }>;
254
249
  /** Compose a Git Worker's root from its one options value. */
255
250
  export declare function oysterGitRoot<Env extends OysterGitWorkerEnv>(options: OysterGitWorkerOptions<Env>): OysterGitRoot<Env>;
@@ -280,6 +275,6 @@ export declare function oysterWorkerGit<Env extends OysterGitWorkerEnv>(env: Env
280
275
  * One selected tenant's whole host from a root's options. Roots reach it through
281
276
  * `oysterGitRoot(options).hostFor`; a cold directory passes its own resolved tenant.
282
277
  */
283
- export declare function oysterGitWorkerHost<Env extends OysterGitWorkerEnv>(env: Env, options: OysterGitWorkerOptions<Env>, selected: ResolvedTenant): OysterGitWorkerHost;
278
+ export declare function oysterGitWorkerHost<Env extends OysterGitWorkerEnv>(env: Env, options: OysterGitWorkerOptions<Env>, selected: ResolvedTenant, request?: Request): Promise<OysterGitWorkerHost>;
284
279
  /** Deploy the MCP owner from the same root as createOysterGitWorker. */
285
280
  export declare function createOysterGitMcpOwner<Env extends OysterGitWorkerEnv>(root: OysterGitRoot<Env>): new (ctx: DurableObjectState, env: Env) => import("./mcp-owner.js").OysterMcpOwner<Env>;
@@ -1,4 +1,3 @@
1
- import * as z from "zod";
2
1
  import { defaultTenant, implicitTenancy, tenantScope, } from "./tenant.js";
3
2
  import { createOysterMcpOwner } from "./mcp-owner.js";
4
3
  import { createAdmittedGit, createInstallationGitAdmission, } from "./git-admission.js";
@@ -15,19 +14,20 @@ import { AppInstanceError, selectionOf } from "./index.js";
15
14
  import { ownerOrKeyAuth } from "./auth.js";
16
15
  import { causeClass } from "./cause-class.js";
17
16
  import { Hono } from "hono";
17
+ import { HTTPException } from "hono/http-exception";
18
18
  import * as Schema from "effect/Schema";
19
19
  import { defineIdFamily } from "@fungi.computer/cairn";
20
20
  import {} from "@fungi.computer/agar/git";
21
21
  import { parseAppMethodCallContext, workflowAttestationSchema, } from "@fungi.computer/puffball/app-method-runtime";
22
22
  import { lsRefsRequest, parseLsRefs, } from "./git.js";
23
- import { createKeyStore, } from "@fungi.computer/ear-tag/keys";
23
+ import { createKeyStore, keyWire, } from "@fungi.computer/ear-tag/keys";
24
24
  import { oysterKeyVocabulary, } from "./keys.js";
25
25
  import { agentCapabilities, agentApprovalRoutes, agentMcpServerRoutes, } from "./agents.js";
26
26
  import { createArtifactsRestNamespace, } from "@fungi.computer/burrow";
27
27
  import { attemptWebhook } from "./webhook-delivery.js";
28
28
  import { createRecipientDeliveries, } from "./event-recipients.js";
29
29
  import { createOyster, oysterPrincipalIsOwner, } from "./host.js";
30
- import { OysterInstance, oysterBindingEntrypoint, oysterBindings, oysterWorkerHost, r2FileSource, r2ArtifactStore, } from "./workers.js";
30
+ import { OysterInstance, oysterBindingEntrypoint, oysterBindings, oysterWorkerHost, r2FileSource, r2ArtifactStore, serveOysterBootCapability, } from "./workers.js";
31
31
  import { registryInstallations } from "./internal/registry-installations.js";
32
32
  const infoSchema = Schema.Struct({
33
33
  name: Schema.String,
@@ -473,7 +473,7 @@ export class OysterKeys extends DurableObject {
473
473
  }
474
474
  /** The live facts behind one key id, for the host's `keys.live` port; `null` for an unknown, revoked, expired or suspended key. */
475
475
  async live(keyId) {
476
- const result = await this.#store.read("host", keyId);
476
+ const result = await this.#store.live({ keyId });
477
477
  if (result.kind !== "ok")
478
478
  return null;
479
479
  return {
@@ -764,30 +764,7 @@ const subscribeSchema = Schema.Struct({
764
764
  const keyNamePattern = /^[a-z0-9][a-z0-9-]{0,39}$/u;
765
765
  /** `invocation.ts:486` uses `team-key:visitor` for anonymous readers; a key can never claim that name. */
766
766
  const reservedKeyName = "visitor";
767
- const issueKeySchema = Schema.Struct({
768
- name: Schema.String.check(Schema.isPattern(keyNamePattern)),
769
- grants: Schema.Unknown,
770
- expiresAt: Schema.optional(Schema.Number),
771
- });
772
- const revokeKeySchema = Schema.Struct({
773
- keyId: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)),
774
- requestId: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_.:-]{1,112}$/u)),
775
- });
776
- const oysterGrantsArraySchema = z
777
- .array(oysterKeyVocabulary.grantSchema)
778
- .min(1)
779
- .max(64);
780
- function keySummary(record) {
781
- return {
782
- keyId: record.keyId,
783
- name: record.name,
784
- grants: record.scope?.grants ?? [],
785
- expiresAt: record.expiresAt,
786
- createdAt: record.createdAt,
787
- lastUsedAt: record.lastUsedAt,
788
- revokedAt: record.revokedAt,
789
- };
790
- }
767
+ const keysWire = keyWire(oysterKeyVocabulary);
791
768
  /** Owner routes for keys and webhooks; `createOyster` mounts them owner-only. */
792
769
  export function gitAdminRoutes(env, scope) {
793
770
  const routes = new Hono();
@@ -799,40 +776,44 @@ export function gitAdminRoutes(env, scope) {
799
776
  const listed = await keys().list(owner.actor.memberId);
800
777
  if (listed.kind === "error")
801
778
  return Response.json({ error: listed.error.code }, { status: 500 });
802
- return Response.json({ keys: listed.value.map(keySummary) });
779
+ return Response.json({ keys: listed.value.map(keysWire.toSummary) });
803
780
  });
804
781
  routes.post("/api/keys", async (context) => {
805
- const body = Schema.decodeUnknownOption(issueKeySchema)(await context.req.json());
806
- if (body._tag === "None")
782
+ const body = keysWire.issue.safeParse(await context.req.json());
783
+ if (!body.success)
807
784
  return Response.json({ error: "Invalid key" }, { status: 400 });
808
- if (body.value.name === reservedKeyName)
785
+ // Named identities and rate policy belong to this host, not the wire grammar.
786
+ if (!keyNamePattern.test(body.data.name) ||
787
+ body.data.name === reservedKeyName ||
788
+ body.data.keyId !== undefined ||
789
+ body.data.rateLimit !== undefined)
809
790
  return Response.json({ error: "Invalid key name" }, { status: 400 });
810
791
  const owner = context.get("principal");
811
792
  if (!oysterPrincipalIsOwner(owner))
812
793
  return Response.json({ error: "Owner only" }, { status: 403 });
813
- const grants = oysterGrantsArraySchema.safeParse(body.value.grants);
814
- if (!grants.success)
815
- return Response.json({ error: "Invalid grants" }, { status: 400 });
816
794
  const issued = await keys().issue(owner.actor.memberId, {
817
- name: body.value.name,
818
- grants: grants.data,
819
- expiresAt: body.value.expiresAt,
795
+ name: body.data.name,
796
+ grants: body.data.grants,
797
+ expiresAt: body.data.expiresAt,
820
798
  });
821
799
  if (issued.kind === "error")
822
800
  return Response.json({ error: issued.error.code }, { status: issued.error.code === "conflict" ? 409 : 400 });
823
- return Response.json({ plaintext: issued.value.key, key: keySummary(issued.value.record) }, { status: 201 });
801
+ return Response.json({
802
+ plaintext: issued.value.key,
803
+ key: keysWire.toSummary(issued.value.record),
804
+ }, { status: 201 });
824
805
  });
825
806
  routes.post("/api/keys/revoke", async (context) => {
826
- const body = Schema.decodeUnknownOption(revokeKeySchema)(await context.req.json());
827
- if (body._tag === "None")
807
+ const body = keysWire.revoke.safeParse(await context.req.json());
808
+ if (!body.success)
828
809
  return Response.json({ error: "Invalid request" }, { status: 400 });
829
810
  const owner = context.get("principal");
830
811
  if (!oysterPrincipalIsOwner(owner))
831
812
  return Response.json({ error: "Owner only" }, { status: 403 });
832
- const revoked = await keys().revoke(owner.actor.memberId, body.value.requestId, body.value.keyId);
813
+ const revoked = await keys().revoke(owner.actor.memberId, body.data.requestId, body.data.keyId);
833
814
  if (revoked.kind === "error")
834
815
  return Response.json({ error: revoked.error.code }, { status: revoked.error.code === "denied" ? 404 : 400 });
835
- return Response.json({ key: keySummary(revoked.value) });
816
+ return Response.json({ key: keysWire.toSummary(revoked.value) });
836
817
  });
837
818
  routes.post("/api/webhooks", async (context) => {
838
819
  const body = Schema.decodeUnknownOption(subscribeSchema)(await context.req.json());
@@ -864,7 +845,11 @@ export function oysterGitRoot(options) {
864
845
  const stateOf = (env) => {
865
846
  let state = environments.get(env);
866
847
  if (state === undefined) {
867
- state = { tenancy: options.tenancy(env), admissions: new WeakMap() };
848
+ state = {
849
+ tenancy: options.tenancy(env),
850
+ admissions: new WeakMap(),
851
+ authentications: new WeakMap(),
852
+ };
868
853
  environments.set(env, state);
869
854
  }
870
855
  return state;
@@ -877,12 +862,12 @@ export function oysterGitRoot(options) {
877
862
  state.admissions.set(request, resolved);
878
863
  return resolved;
879
864
  },
880
- hostFor(env, scope) {
865
+ hostFor(env, scope, request) {
881
866
  const state = stateOf(env);
882
867
  return oysterGitWorkerHost(env, options, {
883
868
  scope,
884
- auth: pinnedTenantAuth(state.tenancy, scope, state.admissions),
885
- });
869
+ auth: pinnedTenantAuth(state.tenancy, scope, state.admissions, state.authentications),
870
+ }, request);
886
871
  },
887
872
  };
888
873
  }
@@ -907,7 +892,19 @@ export function createOysterGitWorker(root) {
907
892
  if (resolved === null)
908
893
  return new Response("Not found", { status: 404 });
909
894
  const { scope } = resolved;
910
- const key = `${scope.tenantId}:${scope.origin}`;
895
+ const boot = await serveOysterBootCapability(request, env, scope);
896
+ if (boot !== null)
897
+ return boot;
898
+ let host;
899
+ try {
900
+ host = await root.hostFor(env, scope, request);
901
+ }
902
+ catch (error) {
903
+ if (error instanceof HTTPException)
904
+ return error.getResponse();
905
+ throw error;
906
+ }
907
+ const key = `${scope.tenantId}:${scope.origin}:${host.computerId ?? ""}`;
911
908
  let entry = hosts.get(key);
912
909
  let cache = true;
913
910
  if (entry === undefined) {
@@ -919,7 +916,7 @@ export function createOysterGitWorker(root) {
919
916
  hosts.delete(idle[0]);
920
917
  }
921
918
  }
922
- entry = { app: createOyster(root.hostFor(env, scope)), active: 0 };
919
+ entry = { app: createOyster(host), active: 0 };
923
920
  if (cache)
924
921
  hosts.set(key, entry);
925
922
  }
@@ -969,7 +966,7 @@ export function oysterWorkerGit(env, options, frame) {
969
966
  if (selected.owner.kind !== "installation" ||
970
967
  selected.owner.id !== current.installationId ||
971
968
  selected.instanceId !== current.instanceId ||
972
- selected.authorityVersion !== current.authorityVersion ||
969
+ selected.authorityVersion !== current.authority.authorityVersion ||
973
970
  selected.appId !== current.release.appId ||
974
971
  selected.releaseId !== current.release.releaseId ||
975
972
  captured.release.appId !== current.release.appId ||
@@ -990,7 +987,8 @@ export function oysterWorkerGit(env, options, frame) {
990
987
  beforeProvider();
991
988
  if (current?.status !== "enabled" ||
992
989
  current.instanceId !== installation.instanceId ||
993
- current.authorityVersion !== installation.authorityVersion ||
990
+ current.authority.authorityVersion !==
991
+ installation.authority.authorityVersion ||
994
992
  current.release.appId !== installation.release.appId ||
995
993
  current.release.releaseId !== installation.release.releaseId ||
996
994
  current.release.artifact.artifactRef !==
@@ -1072,7 +1070,7 @@ export function oysterWorkerGit(env, options, frame) {
1072
1070
  * One selected tenant's whole host from a root's options. Roots reach it through
1073
1071
  * `oysterGitRoot(options).hostFor`; a cold directory passes its own resolved tenant.
1074
1072
  */
1075
- export function oysterGitWorkerHost(env, options, selected) {
1073
+ export async function oysterGitWorkerHost(env, options, selected, request) {
1076
1074
  const { scope, auth } = selected;
1077
1075
  // The one path to a key's liveness: the stored owner id is re-checked
1078
1076
  // against the live, current owner on every read (S3 inherits this port,
@@ -1090,7 +1088,7 @@ export function oysterGitWorkerHost(env, options, selected) {
1090
1088
  });
1091
1089
  return current !== null && oysterPrincipalIsOwner(current) ? key : null;
1092
1090
  };
1093
- const workspace = options.workspace?.(env, keysLive) ?? {
1091
+ const workspace = (await options.workspace?.(env, keysLive, scope, request === undefined ? undefined : { request, auth })) ?? {
1094
1092
  computerId: scope.computerName("workspace"),
1095
1093
  files: r2FileSource(env.RELEASES, scope.workspacePrefix),
1096
1094
  };
@@ -1133,6 +1131,8 @@ export function oysterGitWorkerHost(env, options, selected) {
1133
1131
  };
1134
1132
  if (options.pageScript !== undefined)
1135
1133
  host = { ...host, pageScript: options.pageScript };
1134
+ if (options.workflows !== undefined)
1135
+ host = { ...host, workflows: options.workflows(env, host) };
1136
1136
  if (options.homeInstallationId !== undefined)
1137
1137
  host = { ...host, homeInstallationId: options.homeInstallationId(env) };
1138
1138
  return host;
@@ -1141,7 +1141,7 @@ export function oysterGitWorkerHost(env, options, selected) {
1141
1141
  * Authenticate against the pinned tenant. A request admitted at the Worker's
1142
1142
  * top reuses that one admission; any other entry resolves its own.
1143
1143
  */
1144
- function pinnedTenantAuth(tenancy, scope, admissions) {
1144
+ function pinnedTenantAuth(tenancy, scope, admissions, authentications) {
1145
1145
  const admitted = async (incoming) => admissions.get(incoming) ?? (await tenancy.resolve(incoming));
1146
1146
  return {
1147
1147
  resolve: async (actor) => {
@@ -1156,7 +1156,12 @@ function pinnedTenantAuth(tenancy, scope, admissions) {
1156
1156
  if (current?.scope.tenantId !== scope.tenantId ||
1157
1157
  current.scope.origin !== scope.origin)
1158
1158
  return { kind: "refused", status: 404 };
1159
- return current.auth.authenticate(incoming);
1159
+ let authenticated = authentications.get(incoming);
1160
+ if (authenticated === undefined) {
1161
+ authenticated = current.auth.authenticate(incoming);
1162
+ authentications.set(incoming, authenticated);
1163
+ }
1164
+ return authenticated;
1160
1165
  },
1161
1166
  login: async (incoming) => {
1162
1167
  const current = await admitted(incoming);