@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.
- package/README.md +396 -72
- package/dist/agents.d.ts +3 -2
- package/dist/agents.js +4 -2
- package/dist/app-builds.d.ts +81 -8
- package/dist/app-builds.js +213 -60
- package/dist/app-storage.d.ts +3 -3
- package/dist/approval-capability.d.ts +5 -0
- package/dist/approval-capability.js +46 -0
- package/dist/approval-home.d.ts +51 -0
- package/dist/approval-home.js +226 -0
- package/dist/apps/catalog.d.ts +335 -0
- package/dist/apps/catalog.js +46 -0
- package/dist/apps/gate.d.ts +20 -6
- package/dist/apps/gate.js +42 -16
- package/dist/apps/index.d.ts +3 -1
- package/dist/apps/index.js +3 -1
- package/dist/apps/installation.d.ts +132 -51
- package/dist/apps/installation.js +176 -70
- package/dist/apps/permissions.d.ts +247 -0
- package/dist/apps/permissions.js +255 -0
- package/dist/boot-capability-route.d.ts +15 -0
- package/dist/boot-capability-route.js +196 -0
- package/dist/boot-capability.d.ts +103 -0
- package/dist/boot-capability.js +141 -0
- package/dist/boot-construction.d.ts +12 -0
- package/dist/boot-construction.js +175 -0
- package/dist/boot-gate.d.ts +267 -0
- package/dist/boot-gate.js +236 -0
- package/dist/boot-host-script.d.ts +2 -0
- package/dist/boot-host-script.js +1 -0
- package/dist/boot-mount.d.ts +21 -0
- package/dist/boot-mount.js +65 -0
- package/dist/browser/boot.d.ts +73 -0
- package/dist/browser/boot.js +502 -0
- package/dist/browser/browser.d.ts +104 -0
- package/dist/browser/browser.js +374 -0
- package/dist/browser/capability-source.d.ts +76 -0
- package/dist/browser/capability-source.js +338 -0
- package/dist/browser/cdp-client.d.ts +56 -0
- package/dist/browser/cdp-client.js +184 -0
- package/dist/browser/errors.d.ts +79 -0
- package/dist/browser/errors.js +32 -0
- package/dist/browser/index.d.ts +16 -0
- package/dist/browser/index.js +14 -0
- package/dist/browser/lease-registry.d.ts +61 -0
- package/dist/browser/lease-registry.js +101 -0
- package/dist/browser/operations.d.ts +225 -0
- package/dist/browser/operations.js +137 -0
- package/dist/browser/page.d.ts +25 -0
- package/dist/browser/page.js +172 -0
- package/dist/browser/quick-reads.d.ts +29 -0
- package/dist/browser/quick-reads.js +160 -0
- package/dist/browser/sessions.d.ts +52 -0
- package/dist/browser/sessions.js +118 -0
- package/dist/browser/url-policy.d.ts +18 -0
- package/dist/browser/url-policy.js +58 -0
- package/dist/build/app-imports.d.ts +12 -0
- package/dist/build/app-imports.js +105 -0
- package/dist/build/build-policy.d.ts +5 -3
- package/dist/build/build-policy.js +5 -3
- package/dist/build/candidate-builder.d.ts +1 -1
- package/dist/build/candidate-cli.mjs +8375 -334
- package/dist/build/local-build.d.ts +2 -0
- package/dist/build/local-build.js +28 -6
- package/dist/build/snapshot-package-store.d.ts +1 -1
- package/dist/build/snapshot-package-store.js +7 -2
- package/dist/build/workspace-build.d.ts +92 -5
- package/dist/build/workspace-build.js +93 -19
- package/dist/build.d.ts +1 -1
- package/dist/build.js +1 -1
- package/dist/catalog-frame.js +1 -1
- package/dist/cloudflare.d.ts +13 -3
- package/dist/cloudflare.js +102 -4
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.js +4 -2
- package/dist/git-auth.d.ts +0 -2
- package/dist/git-auth.js +0 -2
- package/dist/git.d.ts +0 -1
- package/dist/git.js +0 -2
- package/dist/host.d.ts +39 -13
- package/dist/host.js +268 -142
- package/dist/index.d.ts +25 -15
- package/dist/index.js +39 -16
- package/dist/install-release-cli.d.ts +2 -0
- package/dist/install-release-cli.js +46 -0
- package/dist/installation-client.d.ts +43 -0
- package/dist/installation-client.js +178 -0
- package/dist/installation-execution.d.ts +11 -0
- package/dist/installation-execution.js +184 -0
- package/dist/installation-operations.d.ts +1437 -0
- package/dist/installation-operations.js +664 -0
- package/dist/installation-store-generation.d.ts +3 -0
- package/dist/installation-store-generation.js +16 -0
- package/dist/instance-id.d.ts +10 -0
- package/dist/instance-id.js +10 -0
- package/dist/instance-refusal.d.ts +11 -0
- package/dist/instance-refusal.js +21 -0
- package/dist/internal/delivery.js +4 -2
- package/dist/internal/host-gate.js +7 -1
- package/dist/internal/registry-installations.js +4 -4
- package/dist/keys.d.ts +5 -1
- package/dist/keys.js +10 -2
- package/dist/machine-link/authority.d.ts +89 -0
- package/dist/machine-link/authority.js +37 -0
- package/dist/machine-link/client.d.ts +66 -0
- package/dist/machine-link/client.js +94 -0
- package/dist/machine-link/delivery.d.ts +18 -0
- package/dist/machine-link/delivery.js +55 -0
- package/dist/machine-link/enrollment.d.ts +111 -0
- package/dist/machine-link/enrollment.js +286 -0
- package/dist/machine-link/fences.d.ts +37 -0
- package/dist/machine-link/fences.js +166 -0
- package/dist/machine-link/home.d.ts +20 -0
- package/dist/machine-link/home.js +24 -0
- package/dist/machine-link/host.d.ts +43 -0
- package/dist/machine-link/host.js +189 -0
- package/dist/machine-link/index.d.ts +15 -0
- package/dist/machine-link/index.js +15 -0
- package/dist/machine-link/link.d.ts +48 -0
- package/dist/machine-link/link.js +223 -0
- package/dist/machine-link/presence.d.ts +10 -0
- package/dist/machine-link/presence.js +25 -0
- package/dist/machine-link/workers.d.ts +6 -0
- package/dist/machine-link/workers.js +6 -0
- package/dist/mcp-owner.d.ts +2 -2
- package/dist/mcp-owner.js +41 -33
- package/dist/mcp.d.ts +15 -3
- package/dist/mcp.js +146 -25
- package/dist/older-oyster-store-refusal.d.ts +4 -0
- package/dist/older-oyster-store-refusal.js +18 -0
- package/dist/older-oyster-store.d.ts +21 -0
- package/dist/older-oyster-store.js +43 -0
- package/dist/pack.d.ts +14 -3
- package/dist/pack.js +44 -19
- package/dist/page/app-access.d.ts +7 -0
- package/dist/page/app-access.js +75 -0
- package/dist/page/boot-host.js +57 -0
- package/dist/page/capabilities.d.ts +3 -3
- package/dist/page/capabilities.js +16 -12
- package/dist/page/computer-handles.d.ts +3 -3
- package/dist/page/computer-target.d.ts +3 -0
- package/dist/page/computer-target.js +9 -0
- package/dist/page/mount.d.ts +23 -3
- package/dist/page/mount.js +152 -16
- package/dist/page-assets.d.ts +22 -0
- package/dist/page-assets.js +28 -0
- package/dist/page-script.d.ts +3 -0
- package/dist/page-script.js +5 -4
- package/dist/release.d.ts +13 -6
- package/dist/release.js +10 -7
- package/dist/root-data.d.ts +16 -6
- package/dist/root-data.js +2 -2
- package/dist/source/index.d.ts +1 -1
- package/dist/source/team-source.d.ts +20 -8
- package/dist/source/team-source.js +39 -10
- package/dist/source/template-seed.d.ts +2 -2
- package/dist/standalone/agent-mail.d.ts +24 -0
- package/dist/standalone/agent-mail.js +62 -0
- package/dist/standalone/agent-store.d.ts +9 -0
- package/dist/standalone/agent-store.js +30 -0
- package/dist/standalone/agents.d.ts +68 -9
- package/dist/standalone/agents.js +428 -56
- package/dist/standalone/app-builds-binding.d.ts +20 -2
- package/dist/standalone/app-builds-binding.js +147 -23
- package/dist/standalone/app-set.d.ts +5 -2
- package/dist/standalone/app-set.js +186 -144
- package/dist/standalone/browser.d.ts +11 -0
- package/dist/standalone/browser.js +72 -0
- package/dist/standalone/builds.d.ts +13 -4
- package/dist/standalone/builds.js +28 -44
- package/dist/standalone/computer-egress.d.ts +2 -0
- package/dist/standalone/computer-egress.js +13 -4
- package/dist/standalone/computer-fleet.d.ts +43 -0
- package/dist/standalone/computer-fleet.js +200 -0
- package/dist/standalone/computer.d.ts +44 -9
- package/dist/standalone/computer.js +129 -22
- package/dist/standalone/install-apps-cli.js +43 -6
- package/dist/standalone/instance.d.ts +2 -2
- package/dist/standalone/instance.js +8 -11
- package/dist/standalone/machine-link.d.ts +354 -0
- package/dist/standalone/machine-link.js +368 -0
- package/dist/standalone/media.d.ts +2 -2
- package/dist/standalone/media.js +19 -16
- package/dist/standalone/preview.d.ts +2 -2
- package/dist/standalone/preview.js +37 -10
- package/dist/standalone/registry.d.ts +151 -9
- package/dist/standalone/registry.js +305 -8
- package/dist/standalone/worker.d.ts +19 -5
- package/dist/standalone/worker.js +90 -54
- package/dist/standalone/workflow-binding.d.ts +18 -22
- package/dist/standalone/workflow-binding.js +18 -18
- package/dist/standalone/workflow-control.d.ts +4 -0
- package/dist/standalone/workflow-control.js +40 -0
- package/dist/standalone/workflow-events.d.ts +2 -2
- package/dist/standalone/workflow-events.js +9 -10
- package/dist/standalone/workflow-git-capture.js +4 -3
- package/dist/standalone/workflow-host.d.ts +162 -161
- package/dist/standalone/workflow-host.js +101 -21
- package/dist/standalone/workflow-node-capture.js +11 -4
- package/dist/standalone/workflow-schedules.d.ts +5 -2
- package/dist/standalone/workflow-schedules.js +9 -10
- package/dist/standalone/workflow-trigger-host.d.ts +65 -5
- package/dist/standalone/workflow-trigger-host.js +2 -2
- package/dist/standalone/workflow-webhooks.d.ts +22 -0
- package/dist/standalone/workflow-webhooks.js +39 -0
- package/dist/standalone/workflows.d.ts +140 -43
- package/dist/standalone/workflows.js +198 -39
- package/dist/storage-frame.d.ts +6 -6
- package/dist/testing/capabilities.js +14 -3
- package/dist/testing/installation.js +26 -2
- package/dist/testing/worker.js +1 -1
- package/dist/testing/workers-runtime.d.ts +8 -2
- package/dist/testing/workers-runtime.js +20 -2
- package/dist/webhook-abuse.d.ts +25 -0
- package/dist/webhook-abuse.js +54 -0
- package/dist/webhook-custody.d.ts +144 -0
- package/dist/webhook-custody.js +222 -0
- package/dist/webhook-ingress.d.ts +36 -0
- package/dist/webhook-ingress.js +146 -0
- package/dist/webhooks.d.ts +111 -0
- package/dist/webhooks.js +189 -0
- package/dist/workers-git.d.ts +12 -17
- package/dist/workers-git.js +64 -52
- package/dist/workers.d.ts +226 -13
- package/dist/workers.js +363 -51
- package/dist/workflow-agent-authority.d.ts +8 -0
- package/dist/workflow-agent-authority.js +42 -0
- package/dist/workflows.d.ts +66 -0
- package/dist/workflows.js +97 -0
- package/docs/api/README.md +63 -37
- package/docs/api/agents.md +17 -0
- package/docs/api/app-storage.md +1 -1
- package/docs/api/apps.md +1394 -543
- package/docs/api/browser/boot.md +132 -0
- package/docs/api/browser.md +1440 -0
- package/docs/api/build-contract.md +38 -25
- package/docs/api/build.md +263 -84
- package/docs/api/cloudflare.md +93 -7
- package/docs/api/git.md +3 -13
- package/docs/api/host.md +944 -118
- package/docs/api/index.md +320 -18
- package/docs/api/keys.md +24 -1
- package/docs/api/mcp.md +59 -12
- package/docs/api/modules.md +33 -40
- package/docs/api/pack.md +57 -8
- package/docs/api/page/mount.md +92 -4
- package/docs/api/workers-git.md +103 -24
- package/docs/api/workers.md +1594 -244
- package/docs/site/agent-quickstart.mdx +28 -14
- package/examples/standalone/worker.ts +3 -1
- package/examples/standalone/wrangler.jsonc +2 -0
- package/package.json +73 -71
- package/dist/installation-consent.d.ts +0 -81
- package/dist/installation-consent.js +0 -84
- package/dist/shell/app-directory.d.ts +0 -542
- package/dist/shell/app-directory.js +0 -196
- package/dist/shell/browser-actions.d.ts +0 -7
- package/dist/shell/browser-actions.js +0 -56
- package/dist/shell/command-host.d.ts +0 -112
- package/dist/shell/command-host.js +0 -502
- package/dist/shell/commands.d.ts +0 -25
- package/dist/shell/commands.js +0 -78
- package/dist/shell/index.d.ts +0 -8
- package/dist/shell/index.js +0 -8
- package/dist/shell/keys.d.ts +0 -41
- package/dist/shell/keys.js +0 -134
- package/dist/shell/shell-module.d.ts +0 -55
- package/dist/shell/shell-module.js +0 -86
- package/dist/shell/window-manager.d.ts +0 -128
- package/dist/shell/window-manager.js +0 -730
- package/dist/shell/workbench-contract.d.ts +0 -68
- package/dist/shell/workbench-directory.d.ts +0 -332
- package/dist/shell/workbench-directory.js +0 -35
- package/dist/shell/workbench-host-types.d.ts +0 -96
- package/dist/shell/workbench-host-types.js +0 -1
- package/dist/shell/workbench-host.d.ts +0 -34
- package/dist/shell/workbench-host.js +0 -352
- package/dist/shell/workbench-model.d.ts +0 -194
- package/dist/shell/workbench-model.js +0 -53
- package/dist/shell/workbench.d.ts +0 -4
- package/dist/shell/workbench.js +0 -2
- package/docs/api/shell/app-directory.md +0 -599
- package/docs/api/shell/command-host.md +0 -224
- package/docs/api/shell/commands.md +0 -197
- package/docs/api/shell/keys.md +0 -183
- package/docs/api/shell/shell-module.md +0 -204
- package/docs/api/shell/window-manager.md +0 -267
- package/docs/api/shell/workbench-directory.md +0 -119
- package/docs/api/shell/workbench.md +0 -205
- package/docs/api/shell.md +0 -1915
- /package/dist/{shell/workbench-contract.js → page/boot-host.d.ts} +0 -0
package/dist/webhooks.js
ADDED
|
@@ -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
|
+
}
|
package/dist/workers-git.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
224
|
-
* omit when this host
|
|
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>;
|
package/dist/workers-git.js
CHANGED
|
@@ -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.
|
|
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
|
|
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(
|
|
780
|
+
return Response.json({ keys: listed.value.map(keysWire.toSummary) });
|
|
800
781
|
});
|
|
801
782
|
routes.post("/api/keys", async (context) => {
|
|
802
|
-
const body =
|
|
803
|
-
if (body.
|
|
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
|
-
|
|
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.
|
|
815
|
-
grants:
|
|
816
|
-
expiresAt: body.
|
|
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({
|
|
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 =
|
|
824
|
-
if (body.
|
|
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.
|
|
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:
|
|
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 = {
|
|
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
|
|
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(
|
|
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 !==
|
|
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
|
-
|
|
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);
|