@fungi.computer/oyster 0.2.0 → 0.3.1

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 (291) hide show
  1. package/README.md +396 -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 +213 -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/older-oyster-store-refusal.d.ts +4 -0
  130. package/dist/older-oyster-store-refusal.js +18 -0
  131. package/dist/older-oyster-store.d.ts +21 -0
  132. package/dist/older-oyster-store.js +43 -0
  133. package/dist/pack.d.ts +14 -3
  134. package/dist/pack.js +44 -19
  135. package/dist/page/app-access.d.ts +7 -0
  136. package/dist/page/app-access.js +75 -0
  137. package/dist/page/boot-host.js +57 -0
  138. package/dist/page/capabilities.d.ts +3 -3
  139. package/dist/page/capabilities.js +16 -12
  140. package/dist/page/computer-handles.d.ts +3 -3
  141. package/dist/page/computer-target.d.ts +3 -0
  142. package/dist/page/computer-target.js +9 -0
  143. package/dist/page/mount.d.ts +23 -3
  144. package/dist/page/mount.js +152 -16
  145. package/dist/page-assets.d.ts +22 -0
  146. package/dist/page-assets.js +28 -0
  147. package/dist/page-script.d.ts +3 -0
  148. package/dist/page-script.js +5 -4
  149. package/dist/release.d.ts +13 -6
  150. package/dist/release.js +10 -7
  151. package/dist/root-data.d.ts +16 -6
  152. package/dist/root-data.js +2 -2
  153. package/dist/source/index.d.ts +1 -1
  154. package/dist/source/team-source.d.ts +20 -8
  155. package/dist/source/team-source.js +39 -10
  156. package/dist/source/template-seed.d.ts +2 -2
  157. package/dist/standalone/agent-mail.d.ts +24 -0
  158. package/dist/standalone/agent-mail.js +62 -0
  159. package/dist/standalone/agent-store.d.ts +9 -0
  160. package/dist/standalone/agent-store.js +30 -0
  161. package/dist/standalone/agents.d.ts +68 -9
  162. package/dist/standalone/agents.js +428 -56
  163. package/dist/standalone/app-builds-binding.d.ts +20 -2
  164. package/dist/standalone/app-builds-binding.js +147 -23
  165. package/dist/standalone/app-set.d.ts +5 -2
  166. package/dist/standalone/app-set.js +186 -144
  167. package/dist/standalone/browser.d.ts +11 -0
  168. package/dist/standalone/browser.js +72 -0
  169. package/dist/standalone/builds.d.ts +13 -4
  170. package/dist/standalone/builds.js +28 -44
  171. package/dist/standalone/computer-egress.d.ts +2 -0
  172. package/dist/standalone/computer-egress.js +13 -4
  173. package/dist/standalone/computer-fleet.d.ts +43 -0
  174. package/dist/standalone/computer-fleet.js +200 -0
  175. package/dist/standalone/computer.d.ts +44 -9
  176. package/dist/standalone/computer.js +129 -22
  177. package/dist/standalone/install-apps-cli.js +43 -6
  178. package/dist/standalone/instance.d.ts +2 -2
  179. package/dist/standalone/instance.js +8 -11
  180. package/dist/standalone/machine-link.d.ts +354 -0
  181. package/dist/standalone/machine-link.js +368 -0
  182. package/dist/standalone/media.d.ts +2 -2
  183. package/dist/standalone/media.js +19 -16
  184. package/dist/standalone/preview.d.ts +2 -2
  185. package/dist/standalone/preview.js +37 -10
  186. package/dist/standalone/registry.d.ts +151 -9
  187. package/dist/standalone/registry.js +305 -8
  188. package/dist/standalone/worker.d.ts +19 -5
  189. package/dist/standalone/worker.js +90 -54
  190. package/dist/standalone/workflow-binding.d.ts +18 -22
  191. package/dist/standalone/workflow-binding.js +18 -18
  192. package/dist/standalone/workflow-control.d.ts +4 -0
  193. package/dist/standalone/workflow-control.js +40 -0
  194. package/dist/standalone/workflow-events.d.ts +2 -2
  195. package/dist/standalone/workflow-events.js +9 -10
  196. package/dist/standalone/workflow-git-capture.js +4 -3
  197. package/dist/standalone/workflow-host.d.ts +162 -161
  198. package/dist/standalone/workflow-host.js +101 -21
  199. package/dist/standalone/workflow-node-capture.js +11 -4
  200. package/dist/standalone/workflow-schedules.d.ts +5 -2
  201. package/dist/standalone/workflow-schedules.js +9 -10
  202. package/dist/standalone/workflow-trigger-host.d.ts +65 -5
  203. package/dist/standalone/workflow-trigger-host.js +2 -2
  204. package/dist/standalone/workflow-webhooks.d.ts +22 -0
  205. package/dist/standalone/workflow-webhooks.js +39 -0
  206. package/dist/standalone/workflows.d.ts +140 -43
  207. package/dist/standalone/workflows.js +198 -39
  208. package/dist/storage-frame.d.ts +6 -6
  209. package/dist/testing/capabilities.js +14 -3
  210. package/dist/testing/installation.js +26 -2
  211. package/dist/testing/worker.js +1 -1
  212. package/dist/testing/workers-runtime.d.ts +8 -2
  213. package/dist/testing/workers-runtime.js +20 -2
  214. package/dist/webhook-abuse.d.ts +25 -0
  215. package/dist/webhook-abuse.js +54 -0
  216. package/dist/webhook-custody.d.ts +144 -0
  217. package/dist/webhook-custody.js +222 -0
  218. package/dist/webhook-ingress.d.ts +36 -0
  219. package/dist/webhook-ingress.js +146 -0
  220. package/dist/webhooks.d.ts +111 -0
  221. package/dist/webhooks.js +189 -0
  222. package/dist/workers-git.d.ts +12 -17
  223. package/dist/workers-git.js +64 -52
  224. package/dist/workers.d.ts +226 -13
  225. package/dist/workers.js +363 -51
  226. package/dist/workflow-agent-authority.d.ts +8 -0
  227. package/dist/workflow-agent-authority.js +42 -0
  228. package/dist/workflows.d.ts +66 -0
  229. package/dist/workflows.js +97 -0
  230. package/docs/api/README.md +63 -37
  231. package/docs/api/agents.md +17 -0
  232. package/docs/api/app-storage.md +1 -1
  233. package/docs/api/apps.md +1394 -543
  234. package/docs/api/browser/boot.md +132 -0
  235. package/docs/api/browser.md +1440 -0
  236. package/docs/api/build-contract.md +38 -25
  237. package/docs/api/build.md +263 -84
  238. package/docs/api/cloudflare.md +93 -7
  239. package/docs/api/git.md +3 -13
  240. package/docs/api/host.md +944 -118
  241. package/docs/api/index.md +320 -18
  242. package/docs/api/keys.md +24 -1
  243. package/docs/api/mcp.md +59 -12
  244. package/docs/api/modules.md +33 -40
  245. package/docs/api/pack.md +57 -8
  246. package/docs/api/page/mount.md +92 -4
  247. package/docs/api/workers-git.md +103 -24
  248. package/docs/api/workers.md +1594 -244
  249. package/docs/site/agent-quickstart.mdx +28 -14
  250. package/examples/standalone/worker.ts +3 -1
  251. package/examples/standalone/wrangler.jsonc +2 -0
  252. package/package.json +73 -71
  253. package/dist/installation-consent.d.ts +0 -81
  254. package/dist/installation-consent.js +0 -84
  255. package/dist/shell/app-directory.d.ts +0 -542
  256. package/dist/shell/app-directory.js +0 -196
  257. package/dist/shell/browser-actions.d.ts +0 -7
  258. package/dist/shell/browser-actions.js +0 -56
  259. package/dist/shell/command-host.d.ts +0 -112
  260. package/dist/shell/command-host.js +0 -502
  261. package/dist/shell/commands.d.ts +0 -25
  262. package/dist/shell/commands.js +0 -78
  263. package/dist/shell/index.d.ts +0 -8
  264. package/dist/shell/index.js +0 -8
  265. package/dist/shell/keys.d.ts +0 -41
  266. package/dist/shell/keys.js +0 -134
  267. package/dist/shell/shell-module.d.ts +0 -55
  268. package/dist/shell/shell-module.js +0 -86
  269. package/dist/shell/window-manager.d.ts +0 -128
  270. package/dist/shell/window-manager.js +0 -730
  271. package/dist/shell/workbench-contract.d.ts +0 -68
  272. package/dist/shell/workbench-directory.d.ts +0 -332
  273. package/dist/shell/workbench-directory.js +0 -35
  274. package/dist/shell/workbench-host-types.d.ts +0 -96
  275. package/dist/shell/workbench-host-types.js +0 -1
  276. package/dist/shell/workbench-host.d.ts +0 -34
  277. package/dist/shell/workbench-host.js +0 -352
  278. package/dist/shell/workbench-model.d.ts +0 -194
  279. package/dist/shell/workbench-model.js +0 -53
  280. package/dist/shell/workbench.d.ts +0 -4
  281. package/dist/shell/workbench.js +0 -2
  282. package/docs/api/shell/app-directory.md +0 -599
  283. package/docs/api/shell/command-host.md +0 -224
  284. package/docs/api/shell/commands.md +0 -197
  285. package/docs/api/shell/keys.md +0 -183
  286. package/docs/api/shell/shell-module.md +0 -204
  287. package/docs/api/shell/window-manager.md +0 -267
  288. package/docs/api/shell/workbench-directory.md +0 -119
  289. package/docs/api/shell/workbench.md +0 -205
  290. package/docs/api/shell.md +0 -1915
  291. /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,20 +14,22 @@ 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
+ import { refuseOlderOysterStore } from "./older-oyster-store-refusal.js";
32
33
  const infoSchema = Schema.Struct({
33
34
  name: Schema.String,
34
35
  defaultBranch: Schema.String,
@@ -473,7 +474,7 @@ export class OysterKeys extends DurableObject {
473
474
  }
474
475
  /** The live facts behind one key id, for the host's `keys.live` port; `null` for an unknown, revoked, expired or suspended key. */
475
476
  async live(keyId) {
476
- const result = await this.#store.read("host", keyId);
477
+ const result = await this.#store.live({ keyId });
477
478
  if (result.kind !== "ok")
478
479
  return null;
479
480
  return {
@@ -764,27 +765,7 @@ const subscribeSchema = Schema.Struct({
764
765
  const keyNamePattern = /^[a-z0-9][a-z0-9-]{0,39}$/u;
765
766
  /** `invocation.ts:486` uses `team-key:visitor` for anonymous readers; a key can never claim that name. */
766
767
  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
- }
768
+ const keysWire = keyWire(oysterKeyVocabulary);
788
769
  /** Owner routes for keys and webhooks; `createOyster` mounts them owner-only. */
789
770
  export function gitAdminRoutes(env, scope) {
790
771
  const routes = new Hono();
@@ -796,40 +777,44 @@ export function gitAdminRoutes(env, scope) {
796
777
  const listed = await keys().list(owner.actor.memberId);
797
778
  if (listed.kind === "error")
798
779
  return Response.json({ error: listed.error.code }, { status: 500 });
799
- return Response.json({ keys: listed.value.map(keySummary) });
780
+ return Response.json({ keys: listed.value.map(keysWire.toSummary) });
800
781
  });
801
782
  routes.post("/api/keys", async (context) => {
802
- const body = Schema.decodeUnknownOption(issueKeySchema)(await context.req.json());
803
- if (body._tag === "None")
783
+ const body = keysWire.issue.safeParse(await context.req.json());
784
+ if (!body.success)
804
785
  return Response.json({ error: "Invalid key" }, { status: 400 });
805
- if (body.value.name === reservedKeyName)
786
+ // Named identities and rate policy belong to this host, not the wire grammar.
787
+ if (!keyNamePattern.test(body.data.name) ||
788
+ body.data.name === reservedKeyName ||
789
+ body.data.keyId !== undefined ||
790
+ body.data.rateLimit !== undefined)
806
791
  return Response.json({ error: "Invalid key name" }, { status: 400 });
807
792
  const owner = context.get("principal");
808
793
  if (!oysterPrincipalIsOwner(owner))
809
794
  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
795
  const issued = await keys().issue(owner.actor.memberId, {
814
- name: body.value.name,
815
- grants: grants.data,
816
- expiresAt: body.value.expiresAt,
796
+ name: body.data.name,
797
+ grants: body.data.grants,
798
+ expiresAt: body.data.expiresAt,
817
799
  });
818
800
  if (issued.kind === "error")
819
801
  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 });
802
+ return Response.json({
803
+ plaintext: issued.value.key,
804
+ key: keysWire.toSummary(issued.value.record),
805
+ }, { status: 201 });
821
806
  });
822
807
  routes.post("/api/keys/revoke", async (context) => {
823
- const body = Schema.decodeUnknownOption(revokeKeySchema)(await context.req.json());
824
- if (body._tag === "None")
808
+ const body = keysWire.revoke.safeParse(await context.req.json());
809
+ if (!body.success)
825
810
  return Response.json({ error: "Invalid request" }, { status: 400 });
826
811
  const owner = context.get("principal");
827
812
  if (!oysterPrincipalIsOwner(owner))
828
813
  return Response.json({ error: "Owner only" }, { status: 403 });
829
- const revoked = await keys().revoke(owner.actor.memberId, body.value.requestId, body.value.keyId);
814
+ const revoked = await keys().revoke(owner.actor.memberId, body.data.requestId, body.data.keyId);
830
815
  if (revoked.kind === "error")
831
816
  return Response.json({ error: revoked.error.code }, { status: revoked.error.code === "denied" ? 404 : 400 });
832
- return Response.json({ key: keySummary(revoked.value) });
817
+ return Response.json({ key: keysWire.toSummary(revoked.value) });
833
818
  });
834
819
  routes.post("/api/webhooks", async (context) => {
835
820
  const body = Schema.decodeUnknownOption(subscribeSchema)(await context.req.json());
@@ -861,7 +846,11 @@ export function oysterGitRoot(options) {
861
846
  const stateOf = (env) => {
862
847
  let state = environments.get(env);
863
848
  if (state === undefined) {
864
- state = { tenancy: options.tenancy(env), admissions: new WeakMap() };
849
+ state = {
850
+ tenancy: options.tenancy(env),
851
+ admissions: new WeakMap(),
852
+ authentications: new WeakMap(),
853
+ };
865
854
  environments.set(env, state);
866
855
  }
867
856
  return state;
@@ -874,12 +863,12 @@ export function oysterGitRoot(options) {
874
863
  state.admissions.set(request, resolved);
875
864
  return resolved;
876
865
  },
877
- hostFor(env, scope) {
866
+ hostFor(env, scope, request) {
878
867
  const state = stateOf(env);
879
868
  return oysterGitWorkerHost(env, options, {
880
869
  scope,
881
- auth: pinnedTenantAuth(state.tenancy, scope, state.admissions),
882
- });
870
+ auth: pinnedTenantAuth(state.tenancy, scope, state.admissions, state.authentications),
871
+ }, request);
883
872
  },
884
873
  };
885
874
  }
@@ -904,7 +893,22 @@ export function createOysterGitWorker(root) {
904
893
  if (resolved === null)
905
894
  return new Response("Not found", { status: 404 });
906
895
  const { scope } = resolved;
907
- const key = `${scope.tenantId}:${scope.origin}`;
896
+ const refused = await refuseOlderOysterStore(request, env, scope);
897
+ if (refused !== null)
898
+ return refused;
899
+ const boot = await serveOysterBootCapability(request, env, scope);
900
+ if (boot !== null)
901
+ return boot;
902
+ let host;
903
+ try {
904
+ host = await root.hostFor(env, scope, request);
905
+ }
906
+ catch (error) {
907
+ if (error instanceof HTTPException)
908
+ return error.getResponse();
909
+ throw error;
910
+ }
911
+ const key = `${scope.tenantId}:${scope.origin}:${host.computerId ?? ""}`;
908
912
  let entry = hosts.get(key);
909
913
  let cache = true;
910
914
  if (entry === undefined) {
@@ -916,7 +920,7 @@ export function createOysterGitWorker(root) {
916
920
  hosts.delete(idle[0]);
917
921
  }
918
922
  }
919
- entry = { app: createOyster(root.hostFor(env, scope)), active: 0 };
923
+ entry = { app: createOyster(host), active: 0 };
920
924
  if (cache)
921
925
  hosts.set(key, entry);
922
926
  }
@@ -966,7 +970,7 @@ export function oysterWorkerGit(env, options, frame) {
966
970
  if (selected.owner.kind !== "installation" ||
967
971
  selected.owner.id !== current.installationId ||
968
972
  selected.instanceId !== current.instanceId ||
969
- selected.authorityVersion !== current.authorityVersion ||
973
+ selected.authorityVersion !== current.authority.authorityVersion ||
970
974
  selected.appId !== current.release.appId ||
971
975
  selected.releaseId !== current.release.releaseId ||
972
976
  captured.release.appId !== current.release.appId ||
@@ -987,7 +991,8 @@ export function oysterWorkerGit(env, options, frame) {
987
991
  beforeProvider();
988
992
  if (current?.status !== "enabled" ||
989
993
  current.instanceId !== installation.instanceId ||
990
- current.authorityVersion !== installation.authorityVersion ||
994
+ current.authority.authorityVersion !==
995
+ installation.authority.authorityVersion ||
991
996
  current.release.appId !== installation.release.appId ||
992
997
  current.release.releaseId !== installation.release.releaseId ||
993
998
  current.release.artifact.artifactRef !==
@@ -1069,7 +1074,7 @@ export function oysterWorkerGit(env, options, frame) {
1069
1074
  * One selected tenant's whole host from a root's options. Roots reach it through
1070
1075
  * `oysterGitRoot(options).hostFor`; a cold directory passes its own resolved tenant.
1071
1076
  */
1072
- export function oysterGitWorkerHost(env, options, selected) {
1077
+ export async function oysterGitWorkerHost(env, options, selected, request) {
1073
1078
  const { scope, auth } = selected;
1074
1079
  // The one path to a key's liveness: the stored owner id is re-checked
1075
1080
  // against the live, current owner on every read (S3 inherits this port,
@@ -1087,7 +1092,7 @@ export function oysterGitWorkerHost(env, options, selected) {
1087
1092
  });
1088
1093
  return current !== null && oysterPrincipalIsOwner(current) ? key : null;
1089
1094
  };
1090
- const workspace = options.workspace?.(env, keysLive) ?? {
1095
+ const workspace = (await options.workspace?.(env, keysLive, scope, request === undefined ? undefined : { request, auth })) ?? {
1091
1096
  computerId: scope.computerName("workspace"),
1092
1097
  files: r2FileSource(env.RELEASES, scope.workspacePrefix),
1093
1098
  };
@@ -1130,6 +1135,8 @@ export function oysterGitWorkerHost(env, options, selected) {
1130
1135
  };
1131
1136
  if (options.pageScript !== undefined)
1132
1137
  host = { ...host, pageScript: options.pageScript };
1138
+ if (options.workflows !== undefined)
1139
+ host = { ...host, workflows: options.workflows(env, host) };
1133
1140
  if (options.homeInstallationId !== undefined)
1134
1141
  host = { ...host, homeInstallationId: options.homeInstallationId(env) };
1135
1142
  return host;
@@ -1138,7 +1145,7 @@ export function oysterGitWorkerHost(env, options, selected) {
1138
1145
  * Authenticate against the pinned tenant. A request admitted at the Worker's
1139
1146
  * top reuses that one admission; any other entry resolves its own.
1140
1147
  */
1141
- function pinnedTenantAuth(tenancy, scope, admissions) {
1148
+ function pinnedTenantAuth(tenancy, scope, admissions, authentications) {
1142
1149
  const admitted = async (incoming) => admissions.get(incoming) ?? (await tenancy.resolve(incoming));
1143
1150
  return {
1144
1151
  resolve: async (actor) => {
@@ -1153,7 +1160,12 @@ function pinnedTenantAuth(tenancy, scope, admissions) {
1153
1160
  if (current?.scope.tenantId !== scope.tenantId ||
1154
1161
  current.scope.origin !== scope.origin)
1155
1162
  return { kind: "refused", status: 404 };
1156
- return current.auth.authenticate(incoming);
1163
+ let authenticated = authentications.get(incoming);
1164
+ if (authenticated === undefined) {
1165
+ authenticated = current.auth.authenticate(incoming);
1166
+ authentications.set(incoming, authenticated);
1167
+ }
1168
+ return authenticated;
1157
1169
  },
1158
1170
  login: async (incoming) => {
1159
1171
  const current = await admitted(incoming);