@fungi.computer/oyster 0.2.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 (287) hide show
  1. package/README.md +376 -72
  2. package/dist/agents.d.ts +3 -2
  3. package/dist/agents.js +4 -2
  4. package/dist/app-builds.d.ts +81 -8
  5. package/dist/app-builds.js +210 -60
  6. package/dist/app-storage.d.ts +3 -3
  7. package/dist/approval-capability.d.ts +5 -0
  8. package/dist/approval-capability.js +46 -0
  9. package/dist/approval-home.d.ts +51 -0
  10. package/dist/approval-home.js +226 -0
  11. package/dist/apps/catalog.d.ts +335 -0
  12. package/dist/apps/catalog.js +46 -0
  13. package/dist/apps/gate.d.ts +20 -6
  14. package/dist/apps/gate.js +42 -16
  15. package/dist/apps/index.d.ts +3 -1
  16. package/dist/apps/index.js +3 -1
  17. package/dist/apps/installation.d.ts +132 -51
  18. package/dist/apps/installation.js +176 -70
  19. package/dist/apps/permissions.d.ts +247 -0
  20. package/dist/apps/permissions.js +255 -0
  21. package/dist/boot-capability-route.d.ts +15 -0
  22. package/dist/boot-capability-route.js +196 -0
  23. package/dist/boot-capability.d.ts +103 -0
  24. package/dist/boot-capability.js +141 -0
  25. package/dist/boot-construction.d.ts +12 -0
  26. package/dist/boot-construction.js +175 -0
  27. package/dist/boot-gate.d.ts +267 -0
  28. package/dist/boot-gate.js +236 -0
  29. package/dist/boot-host-script.d.ts +2 -0
  30. package/dist/boot-host-script.js +1 -0
  31. package/dist/boot-mount.d.ts +21 -0
  32. package/dist/boot-mount.js +65 -0
  33. package/dist/browser/boot.d.ts +73 -0
  34. package/dist/browser/boot.js +502 -0
  35. package/dist/browser/browser.d.ts +104 -0
  36. package/dist/browser/browser.js +374 -0
  37. package/dist/browser/capability-source.d.ts +76 -0
  38. package/dist/browser/capability-source.js +338 -0
  39. package/dist/browser/cdp-client.d.ts +56 -0
  40. package/dist/browser/cdp-client.js +184 -0
  41. package/dist/browser/errors.d.ts +79 -0
  42. package/dist/browser/errors.js +32 -0
  43. package/dist/browser/index.d.ts +16 -0
  44. package/dist/browser/index.js +14 -0
  45. package/dist/browser/lease-registry.d.ts +61 -0
  46. package/dist/browser/lease-registry.js +101 -0
  47. package/dist/browser/operations.d.ts +225 -0
  48. package/dist/browser/operations.js +137 -0
  49. package/dist/browser/page.d.ts +25 -0
  50. package/dist/browser/page.js +172 -0
  51. package/dist/browser/quick-reads.d.ts +29 -0
  52. package/dist/browser/quick-reads.js +160 -0
  53. package/dist/browser/sessions.d.ts +52 -0
  54. package/dist/browser/sessions.js +118 -0
  55. package/dist/browser/url-policy.d.ts +18 -0
  56. package/dist/browser/url-policy.js +58 -0
  57. package/dist/build/app-imports.d.ts +12 -0
  58. package/dist/build/app-imports.js +105 -0
  59. package/dist/build/build-policy.d.ts +5 -3
  60. package/dist/build/build-policy.js +5 -3
  61. package/dist/build/candidate-builder.d.ts +1 -1
  62. package/dist/build/candidate-cli.mjs +8375 -334
  63. package/dist/build/local-build.d.ts +2 -0
  64. package/dist/build/local-build.js +28 -6
  65. package/dist/build/snapshot-package-store.d.ts +1 -1
  66. package/dist/build/snapshot-package-store.js +7 -2
  67. package/dist/build/workspace-build.d.ts +92 -5
  68. package/dist/build/workspace-build.js +93 -19
  69. package/dist/build.d.ts +1 -1
  70. package/dist/build.js +1 -1
  71. package/dist/catalog-frame.js +1 -1
  72. package/dist/cloudflare.d.ts +13 -3
  73. package/dist/cloudflare.js +102 -4
  74. package/dist/frame-binding-wire-script.js +1 -1
  75. package/dist/frame-binding-wire.js +4 -2
  76. package/dist/git-auth.d.ts +0 -2
  77. package/dist/git-auth.js +0 -2
  78. package/dist/git.d.ts +0 -1
  79. package/dist/git.js +0 -2
  80. package/dist/host.d.ts +39 -13
  81. package/dist/host.js +268 -142
  82. package/dist/index.d.ts +25 -15
  83. package/dist/index.js +39 -16
  84. package/dist/install-release-cli.d.ts +2 -0
  85. package/dist/install-release-cli.js +46 -0
  86. package/dist/installation-client.d.ts +43 -0
  87. package/dist/installation-client.js +178 -0
  88. package/dist/installation-execution.d.ts +11 -0
  89. package/dist/installation-execution.js +184 -0
  90. package/dist/installation-operations.d.ts +1437 -0
  91. package/dist/installation-operations.js +664 -0
  92. package/dist/installation-store-generation.d.ts +3 -0
  93. package/dist/installation-store-generation.js +16 -0
  94. package/dist/instance-id.d.ts +10 -0
  95. package/dist/instance-id.js +10 -0
  96. package/dist/instance-refusal.d.ts +11 -0
  97. package/dist/instance-refusal.js +21 -0
  98. package/dist/internal/delivery.js +4 -2
  99. package/dist/internal/host-gate.js +7 -1
  100. package/dist/internal/registry-installations.js +4 -4
  101. package/dist/keys.d.ts +5 -1
  102. package/dist/keys.js +10 -2
  103. package/dist/machine-link/authority.d.ts +89 -0
  104. package/dist/machine-link/authority.js +37 -0
  105. package/dist/machine-link/client.d.ts +66 -0
  106. package/dist/machine-link/client.js +94 -0
  107. package/dist/machine-link/delivery.d.ts +18 -0
  108. package/dist/machine-link/delivery.js +55 -0
  109. package/dist/machine-link/enrollment.d.ts +111 -0
  110. package/dist/machine-link/enrollment.js +286 -0
  111. package/dist/machine-link/fences.d.ts +37 -0
  112. package/dist/machine-link/fences.js +166 -0
  113. package/dist/machine-link/home.d.ts +20 -0
  114. package/dist/machine-link/home.js +24 -0
  115. package/dist/machine-link/host.d.ts +43 -0
  116. package/dist/machine-link/host.js +189 -0
  117. package/dist/machine-link/index.d.ts +15 -0
  118. package/dist/machine-link/index.js +15 -0
  119. package/dist/machine-link/link.d.ts +48 -0
  120. package/dist/machine-link/link.js +223 -0
  121. package/dist/machine-link/presence.d.ts +10 -0
  122. package/dist/machine-link/presence.js +25 -0
  123. package/dist/machine-link/workers.d.ts +6 -0
  124. package/dist/machine-link/workers.js +6 -0
  125. package/dist/mcp-owner.d.ts +2 -2
  126. package/dist/mcp-owner.js +41 -33
  127. package/dist/mcp.d.ts +15 -3
  128. package/dist/mcp.js +146 -25
  129. package/dist/pack.d.ts +14 -3
  130. package/dist/pack.js +44 -19
  131. package/dist/page/app-access.d.ts +7 -0
  132. package/dist/page/app-access.js +75 -0
  133. package/dist/page/boot-host.js +57 -0
  134. package/dist/page/capabilities.d.ts +3 -3
  135. package/dist/page/capabilities.js +16 -12
  136. package/dist/page/computer-handles.d.ts +3 -3
  137. package/dist/page/computer-target.d.ts +3 -0
  138. package/dist/page/computer-target.js +9 -0
  139. package/dist/page/mount.d.ts +23 -3
  140. package/dist/page/mount.js +152 -16
  141. package/dist/page-assets.d.ts +22 -0
  142. package/dist/page-assets.js +28 -0
  143. package/dist/page-script.d.ts +3 -0
  144. package/dist/page-script.js +5 -4
  145. package/dist/release.d.ts +13 -6
  146. package/dist/release.js +10 -7
  147. package/dist/root-data.d.ts +16 -6
  148. package/dist/root-data.js +2 -2
  149. package/dist/source/index.d.ts +1 -1
  150. package/dist/source/team-source.d.ts +20 -8
  151. package/dist/source/team-source.js +39 -10
  152. package/dist/source/template-seed.d.ts +2 -2
  153. package/dist/standalone/agent-mail.d.ts +24 -0
  154. package/dist/standalone/agent-mail.js +62 -0
  155. package/dist/standalone/agent-store.d.ts +9 -0
  156. package/dist/standalone/agent-store.js +30 -0
  157. package/dist/standalone/agents.d.ts +68 -9
  158. package/dist/standalone/agents.js +428 -56
  159. package/dist/standalone/app-builds-binding.d.ts +20 -2
  160. package/dist/standalone/app-builds-binding.js +147 -23
  161. package/dist/standalone/app-set.d.ts +5 -2
  162. package/dist/standalone/app-set.js +174 -143
  163. package/dist/standalone/browser.d.ts +11 -0
  164. package/dist/standalone/browser.js +72 -0
  165. package/dist/standalone/builds.d.ts +13 -4
  166. package/dist/standalone/builds.js +28 -44
  167. package/dist/standalone/computer-egress.d.ts +2 -0
  168. package/dist/standalone/computer-egress.js +13 -4
  169. package/dist/standalone/computer-fleet.d.ts +43 -0
  170. package/dist/standalone/computer-fleet.js +200 -0
  171. package/dist/standalone/computer.d.ts +44 -9
  172. package/dist/standalone/computer.js +129 -22
  173. package/dist/standalone/install-apps-cli.js +43 -6
  174. package/dist/standalone/instance.d.ts +2 -2
  175. package/dist/standalone/instance.js +8 -11
  176. package/dist/standalone/machine-link.d.ts +354 -0
  177. package/dist/standalone/machine-link.js +368 -0
  178. package/dist/standalone/media.d.ts +2 -2
  179. package/dist/standalone/media.js +19 -16
  180. package/dist/standalone/preview.d.ts +2 -2
  181. package/dist/standalone/preview.js +37 -10
  182. package/dist/standalone/registry.d.ts +149 -9
  183. package/dist/standalone/registry.js +299 -8
  184. package/dist/standalone/worker.d.ts +19 -5
  185. package/dist/standalone/worker.js +90 -54
  186. package/dist/standalone/workflow-binding.d.ts +18 -22
  187. package/dist/standalone/workflow-binding.js +18 -18
  188. package/dist/standalone/workflow-control.d.ts +4 -0
  189. package/dist/standalone/workflow-control.js +40 -0
  190. package/dist/standalone/workflow-events.d.ts +2 -2
  191. package/dist/standalone/workflow-events.js +9 -10
  192. package/dist/standalone/workflow-git-capture.js +4 -3
  193. package/dist/standalone/workflow-host.d.ts +162 -161
  194. package/dist/standalone/workflow-host.js +101 -21
  195. package/dist/standalone/workflow-node-capture.js +11 -4
  196. package/dist/standalone/workflow-schedules.d.ts +5 -2
  197. package/dist/standalone/workflow-schedules.js +9 -10
  198. package/dist/standalone/workflow-trigger-host.d.ts +65 -5
  199. package/dist/standalone/workflow-trigger-host.js +2 -2
  200. package/dist/standalone/workflow-webhooks.d.ts +22 -0
  201. package/dist/standalone/workflow-webhooks.js +39 -0
  202. package/dist/standalone/workflows.d.ts +140 -43
  203. package/dist/standalone/workflows.js +198 -39
  204. package/dist/storage-frame.d.ts +6 -6
  205. package/dist/testing/capabilities.js +14 -3
  206. package/dist/testing/installation.js +26 -2
  207. package/dist/testing/worker.js +1 -1
  208. package/dist/testing/workers-runtime.d.ts +8 -2
  209. package/dist/testing/workers-runtime.js +20 -2
  210. package/dist/webhook-abuse.d.ts +25 -0
  211. package/dist/webhook-abuse.js +54 -0
  212. package/dist/webhook-custody.d.ts +144 -0
  213. package/dist/webhook-custody.js +222 -0
  214. package/dist/webhook-ingress.d.ts +36 -0
  215. package/dist/webhook-ingress.js +146 -0
  216. package/dist/webhooks.d.ts +111 -0
  217. package/dist/webhooks.js +189 -0
  218. package/dist/workers-git.d.ts +12 -17
  219. package/dist/workers-git.js +60 -52
  220. package/dist/workers.d.ts +224 -13
  221. package/dist/workers.js +351 -51
  222. package/dist/workflow-agent-authority.d.ts +8 -0
  223. package/dist/workflow-agent-authority.js +42 -0
  224. package/dist/workflows.d.ts +66 -0
  225. package/dist/workflows.js +97 -0
  226. package/docs/api/README.md +63 -37
  227. package/docs/api/agents.md +17 -0
  228. package/docs/api/app-storage.md +1 -1
  229. package/docs/api/apps.md +1394 -543
  230. package/docs/api/browser/boot.md +132 -0
  231. package/docs/api/browser.md +1440 -0
  232. package/docs/api/build-contract.md +38 -25
  233. package/docs/api/build.md +263 -84
  234. package/docs/api/cloudflare.md +93 -7
  235. package/docs/api/git.md +3 -13
  236. package/docs/api/host.md +944 -118
  237. package/docs/api/index.md +320 -18
  238. package/docs/api/keys.md +24 -1
  239. package/docs/api/mcp.md +59 -12
  240. package/docs/api/modules.md +33 -40
  241. package/docs/api/pack.md +57 -8
  242. package/docs/api/page/mount.md +92 -4
  243. package/docs/api/workers-git.md +103 -24
  244. package/docs/api/workers.md +1581 -244
  245. package/docs/site/agent-quickstart.mdx +28 -14
  246. package/examples/standalone/worker.ts +3 -1
  247. package/examples/standalone/wrangler.jsonc +2 -0
  248. package/package.json +73 -71
  249. package/dist/installation-consent.d.ts +0 -81
  250. package/dist/installation-consent.js +0 -84
  251. package/dist/shell/app-directory.d.ts +0 -542
  252. package/dist/shell/app-directory.js +0 -196
  253. package/dist/shell/browser-actions.d.ts +0 -7
  254. package/dist/shell/browser-actions.js +0 -56
  255. package/dist/shell/command-host.d.ts +0 -112
  256. package/dist/shell/command-host.js +0 -502
  257. package/dist/shell/commands.d.ts +0 -25
  258. package/dist/shell/commands.js +0 -78
  259. package/dist/shell/index.d.ts +0 -8
  260. package/dist/shell/index.js +0 -8
  261. package/dist/shell/keys.d.ts +0 -41
  262. package/dist/shell/keys.js +0 -134
  263. package/dist/shell/shell-module.d.ts +0 -55
  264. package/dist/shell/shell-module.js +0 -86
  265. package/dist/shell/window-manager.d.ts +0 -128
  266. package/dist/shell/window-manager.js +0 -730
  267. package/dist/shell/workbench-contract.d.ts +0 -68
  268. package/dist/shell/workbench-directory.d.ts +0 -332
  269. package/dist/shell/workbench-directory.js +0 -35
  270. package/dist/shell/workbench-host-types.d.ts +0 -96
  271. package/dist/shell/workbench-host-types.js +0 -1
  272. package/dist/shell/workbench-host.d.ts +0 -34
  273. package/dist/shell/workbench-host.js +0 -352
  274. package/dist/shell/workbench-model.d.ts +0 -194
  275. package/dist/shell/workbench-model.js +0 -53
  276. package/dist/shell/workbench.d.ts +0 -4
  277. package/dist/shell/workbench.js +0 -2
  278. package/docs/api/shell/app-directory.md +0 -599
  279. package/docs/api/shell/command-host.md +0 -224
  280. package/docs/api/shell/commands.md +0 -197
  281. package/docs/api/shell/keys.md +0 -183
  282. package/docs/api/shell/shell-module.md +0 -204
  283. package/docs/api/shell/window-manager.md +0 -267
  284. package/docs/api/shell/workbench-directory.md +0 -119
  285. package/docs/api/shell/workbench.md +0 -205
  286. package/docs/api/shell.md +0 -1915
  287. /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,27 +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.lazy(() => z.array(oysterKeyVocabulary.grantSchema).min(1).max(64));
777
- function keySummary(record) {
778
- return {
779
- keyId: record.keyId,
780
- name: record.name,
781
- grants: record.scope?.grants ?? [],
782
- expiresAt: record.expiresAt,
783
- createdAt: record.createdAt,
784
- lastUsedAt: record.lastUsedAt,
785
- revokedAt: record.revokedAt,
786
- };
787
- }
767
+ const keysWire = keyWire(oysterKeyVocabulary);
788
768
  /** Owner routes for keys and webhooks; `createOyster` mounts them owner-only. */
789
769
  export function gitAdminRoutes(env, scope) {
790
770
  const routes = new Hono();
@@ -796,40 +776,44 @@ export function gitAdminRoutes(env, scope) {
796
776
  const listed = await keys().list(owner.actor.memberId);
797
777
  if (listed.kind === "error")
798
778
  return Response.json({ error: listed.error.code }, { status: 500 });
799
- return Response.json({ keys: listed.value.map(keySummary) });
779
+ return Response.json({ keys: listed.value.map(keysWire.toSummary) });
800
780
  });
801
781
  routes.post("/api/keys", async (context) => {
802
- const body = Schema.decodeUnknownOption(issueKeySchema)(await context.req.json());
803
- if (body._tag === "None")
782
+ const body = keysWire.issue.safeParse(await context.req.json());
783
+ if (!body.success)
804
784
  return Response.json({ error: "Invalid key" }, { status: 400 });
805
- 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)
806
790
  return Response.json({ error: "Invalid key name" }, { status: 400 });
807
791
  const owner = context.get("principal");
808
792
  if (!oysterPrincipalIsOwner(owner))
809
793
  return Response.json({ error: "Owner only" }, { status: 403 });
810
- const grants = oysterGrantsArraySchema.safeParse(body.value.grants);
811
- if (!grants.success)
812
- return Response.json({ error: "Invalid grants" }, { status: 400 });
813
794
  const issued = await keys().issue(owner.actor.memberId, {
814
- name: body.value.name,
815
- grants: grants.data,
816
- expiresAt: body.value.expiresAt,
795
+ name: body.data.name,
796
+ grants: body.data.grants,
797
+ expiresAt: body.data.expiresAt,
817
798
  });
818
799
  if (issued.kind === "error")
819
800
  return Response.json({ error: issued.error.code }, { status: issued.error.code === "conflict" ? 409 : 400 });
820
- 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 });
821
805
  });
822
806
  routes.post("/api/keys/revoke", async (context) => {
823
- const body = Schema.decodeUnknownOption(revokeKeySchema)(await context.req.json());
824
- if (body._tag === "None")
807
+ const body = keysWire.revoke.safeParse(await context.req.json());
808
+ if (!body.success)
825
809
  return Response.json({ error: "Invalid request" }, { status: 400 });
826
810
  const owner = context.get("principal");
827
811
  if (!oysterPrincipalIsOwner(owner))
828
812
  return Response.json({ error: "Owner only" }, { status: 403 });
829
- 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);
830
814
  if (revoked.kind === "error")
831
815
  return Response.json({ error: revoked.error.code }, { status: revoked.error.code === "denied" ? 404 : 400 });
832
- return Response.json({ key: keySummary(revoked.value) });
816
+ return Response.json({ key: keysWire.toSummary(revoked.value) });
833
817
  });
834
818
  routes.post("/api/webhooks", async (context) => {
835
819
  const body = Schema.decodeUnknownOption(subscribeSchema)(await context.req.json());
@@ -861,7 +845,11 @@ export function oysterGitRoot(options) {
861
845
  const stateOf = (env) => {
862
846
  let state = environments.get(env);
863
847
  if (state === undefined) {
864
- state = { tenancy: options.tenancy(env), admissions: new WeakMap() };
848
+ state = {
849
+ tenancy: options.tenancy(env),
850
+ admissions: new WeakMap(),
851
+ authentications: new WeakMap(),
852
+ };
865
853
  environments.set(env, state);
866
854
  }
867
855
  return state;
@@ -874,12 +862,12 @@ export function oysterGitRoot(options) {
874
862
  state.admissions.set(request, resolved);
875
863
  return resolved;
876
864
  },
877
- hostFor(env, scope) {
865
+ hostFor(env, scope, request) {
878
866
  const state = stateOf(env);
879
867
  return oysterGitWorkerHost(env, options, {
880
868
  scope,
881
- auth: pinnedTenantAuth(state.tenancy, scope, state.admissions),
882
- });
869
+ auth: pinnedTenantAuth(state.tenancy, scope, state.admissions, state.authentications),
870
+ }, request);
883
871
  },
884
872
  };
885
873
  }
@@ -904,7 +892,19 @@ export function createOysterGitWorker(root) {
904
892
  if (resolved === null)
905
893
  return new Response("Not found", { status: 404 });
906
894
  const { scope } = resolved;
907
- 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 ?? ""}`;
908
908
  let entry = hosts.get(key);
909
909
  let cache = true;
910
910
  if (entry === undefined) {
@@ -916,7 +916,7 @@ export function createOysterGitWorker(root) {
916
916
  hosts.delete(idle[0]);
917
917
  }
918
918
  }
919
- entry = { app: createOyster(root.hostFor(env, scope)), active: 0 };
919
+ entry = { app: createOyster(host), active: 0 };
920
920
  if (cache)
921
921
  hosts.set(key, entry);
922
922
  }
@@ -966,7 +966,7 @@ export function oysterWorkerGit(env, options, frame) {
966
966
  if (selected.owner.kind !== "installation" ||
967
967
  selected.owner.id !== current.installationId ||
968
968
  selected.instanceId !== current.instanceId ||
969
- selected.authorityVersion !== current.authorityVersion ||
969
+ selected.authorityVersion !== current.authority.authorityVersion ||
970
970
  selected.appId !== current.release.appId ||
971
971
  selected.releaseId !== current.release.releaseId ||
972
972
  captured.release.appId !== current.release.appId ||
@@ -987,7 +987,8 @@ export function oysterWorkerGit(env, options, frame) {
987
987
  beforeProvider();
988
988
  if (current?.status !== "enabled" ||
989
989
  current.instanceId !== installation.instanceId ||
990
- current.authorityVersion !== installation.authorityVersion ||
990
+ current.authority.authorityVersion !==
991
+ installation.authority.authorityVersion ||
991
992
  current.release.appId !== installation.release.appId ||
992
993
  current.release.releaseId !== installation.release.releaseId ||
993
994
  current.release.artifact.artifactRef !==
@@ -1069,7 +1070,7 @@ export function oysterWorkerGit(env, options, frame) {
1069
1070
  * One selected tenant's whole host from a root's options. Roots reach it through
1070
1071
  * `oysterGitRoot(options).hostFor`; a cold directory passes its own resolved tenant.
1071
1072
  */
1072
- export function oysterGitWorkerHost(env, options, selected) {
1073
+ export async function oysterGitWorkerHost(env, options, selected, request) {
1073
1074
  const { scope, auth } = selected;
1074
1075
  // The one path to a key's liveness: the stored owner id is re-checked
1075
1076
  // against the live, current owner on every read (S3 inherits this port,
@@ -1087,7 +1088,7 @@ export function oysterGitWorkerHost(env, options, selected) {
1087
1088
  });
1088
1089
  return current !== null && oysterPrincipalIsOwner(current) ? key : null;
1089
1090
  };
1090
- const workspace = options.workspace?.(env, keysLive) ?? {
1091
+ const workspace = (await options.workspace?.(env, keysLive, scope, request === undefined ? undefined : { request, auth })) ?? {
1091
1092
  computerId: scope.computerName("workspace"),
1092
1093
  files: r2FileSource(env.RELEASES, scope.workspacePrefix),
1093
1094
  };
@@ -1130,6 +1131,8 @@ export function oysterGitWorkerHost(env, options, selected) {
1130
1131
  };
1131
1132
  if (options.pageScript !== undefined)
1132
1133
  host = { ...host, pageScript: options.pageScript };
1134
+ if (options.workflows !== undefined)
1135
+ host = { ...host, workflows: options.workflows(env, host) };
1133
1136
  if (options.homeInstallationId !== undefined)
1134
1137
  host = { ...host, homeInstallationId: options.homeInstallationId(env) };
1135
1138
  return host;
@@ -1138,7 +1141,7 @@ export function oysterGitWorkerHost(env, options, selected) {
1138
1141
  * Authenticate against the pinned tenant. A request admitted at the Worker's
1139
1142
  * top reuses that one admission; any other entry resolves its own.
1140
1143
  */
1141
- function pinnedTenantAuth(tenancy, scope, admissions) {
1144
+ function pinnedTenantAuth(tenancy, scope, admissions, authentications) {
1142
1145
  const admitted = async (incoming) => admissions.get(incoming) ?? (await tenancy.resolve(incoming));
1143
1146
  return {
1144
1147
  resolve: async (actor) => {
@@ -1153,7 +1156,12 @@ function pinnedTenantAuth(tenancy, scope, admissions) {
1153
1156
  if (current?.scope.tenantId !== scope.tenantId ||
1154
1157
  current.scope.origin !== scope.origin)
1155
1158
  return { kind: "refused", status: 404 };
1156
- 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;
1157
1165
  },
1158
1166
  login: async (incoming) => {
1159
1167
  const current = await admitted(incoming);