@fungi.computer/oyster 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +376 -72
- package/dist/agent-avatars.d.ts +103 -0
- package/dist/agent-avatars.js +66 -0
- package/dist/agents.d.ts +15 -4
- package/dist/agents.js +43 -4
- package/dist/app-builds.d.ts +81 -8
- package/dist/app-builds.js +210 -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 +26 -12
- package/dist/apps/gate.js +48 -22
- package/dist/apps/index.d.ts +3 -1
- package/dist/apps/index.js +3 -1
- package/dist/apps/installation.d.ts +147 -66
- package/dist/apps/installation.js +193 -86
- 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 +8428 -337
- 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-egress.js +4 -4
- 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 +106 -8
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.d.ts +1 -1
- package/dist/frame-binding-wire.js +5 -3
- package/dist/git-auth.d.ts +0 -2
- package/dist/git-auth.js +0 -2
- package/dist/git-journal.js +4 -4
- 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 +26 -15
- package/dist/index.js +40 -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/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 -7
- 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 +78 -12
- package/dist/standalone/agents.js +470 -59
- package/dist/standalone/app-builds-binding.d.ts +20 -2
- package/dist/standalone/app-builds-binding.js +151 -27
- package/dist/standalone/app-set.d.ts +5 -2
- package/dist/standalone/app-set.js +174 -143
- 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 +132 -25
- 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 +39 -12
- package/dist/standalone/registry.d.ts +149 -9
- package/dist/standalone/registry.js +299 -8
- package/dist/standalone/worker.d.ts +19 -5
- package/dist/standalone/worker.js +90 -54
- package/dist/standalone/workflow-binding.d.ts +28 -32
- package/dist/standalone/workflow-binding.js +20 -20
- 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 +177 -152
- package/dist/standalone/workflow-host.js +137 -30
- 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 +88 -6
- 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 +203 -50
- 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 +60 -55
- package/dist/workers.d.ts +224 -13
- package/dist/workers.js +351 -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 +1581 -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 -69
- package/dist/installation-consent.d.ts +0 -79
- package/dist/installation-consent.js +0 -82
- package/dist/shell/app-directory.d.ts +0 -542
- package/dist/shell/app-directory.js +0 -199
- 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 -124
- package/dist/shell/window-manager.js +0 -711
- 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 -347
- package/dist/shell/workbench-model.d.ts +0 -192
- 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,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.
|
|
476
|
+
const result = await this.#store.live({ keyId });
|
|
477
477
|
if (result.kind !== "ok")
|
|
478
478
|
return null;
|
|
479
479
|
return {
|
|
@@ -764,30 +764,7 @@ const subscribeSchema = Schema.Struct({
|
|
|
764
764
|
const keyNamePattern = /^[a-z0-9][a-z0-9-]{0,39}$/u;
|
|
765
765
|
/** `invocation.ts:486` uses `team-key:visitor` for anonymous readers; a key can never claim that name. */
|
|
766
766
|
const reservedKeyName = "visitor";
|
|
767
|
-
const
|
|
768
|
-
name: Schema.String.check(Schema.isPattern(keyNamePattern)),
|
|
769
|
-
grants: Schema.Unknown,
|
|
770
|
-
expiresAt: Schema.optional(Schema.Number),
|
|
771
|
-
});
|
|
772
|
-
const revokeKeySchema = Schema.Struct({
|
|
773
|
-
keyId: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)),
|
|
774
|
-
requestId: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_.:-]{1,112}$/u)),
|
|
775
|
-
});
|
|
776
|
-
const oysterGrantsArraySchema = z
|
|
777
|
-
.array(oysterKeyVocabulary.grantSchema)
|
|
778
|
-
.min(1)
|
|
779
|
-
.max(64);
|
|
780
|
-
function keySummary(record) {
|
|
781
|
-
return {
|
|
782
|
-
keyId: record.keyId,
|
|
783
|
-
name: record.name,
|
|
784
|
-
grants: record.scope?.grants ?? [],
|
|
785
|
-
expiresAt: record.expiresAt,
|
|
786
|
-
createdAt: record.createdAt,
|
|
787
|
-
lastUsedAt: record.lastUsedAt,
|
|
788
|
-
revokedAt: record.revokedAt,
|
|
789
|
-
};
|
|
790
|
-
}
|
|
767
|
+
const keysWire = keyWire(oysterKeyVocabulary);
|
|
791
768
|
/** Owner routes for keys and webhooks; `createOyster` mounts them owner-only. */
|
|
792
769
|
export function gitAdminRoutes(env, scope) {
|
|
793
770
|
const routes = new Hono();
|
|
@@ -799,40 +776,44 @@ export function gitAdminRoutes(env, scope) {
|
|
|
799
776
|
const listed = await keys().list(owner.actor.memberId);
|
|
800
777
|
if (listed.kind === "error")
|
|
801
778
|
return Response.json({ error: listed.error.code }, { status: 500 });
|
|
802
|
-
return Response.json({ keys: listed.value.map(
|
|
779
|
+
return Response.json({ keys: listed.value.map(keysWire.toSummary) });
|
|
803
780
|
});
|
|
804
781
|
routes.post("/api/keys", async (context) => {
|
|
805
|
-
const body =
|
|
806
|
-
if (body.
|
|
782
|
+
const body = keysWire.issue.safeParse(await context.req.json());
|
|
783
|
+
if (!body.success)
|
|
807
784
|
return Response.json({ error: "Invalid key" }, { status: 400 });
|
|
808
|
-
|
|
785
|
+
// Named identities and rate policy belong to this host, not the wire grammar.
|
|
786
|
+
if (!keyNamePattern.test(body.data.name) ||
|
|
787
|
+
body.data.name === reservedKeyName ||
|
|
788
|
+
body.data.keyId !== undefined ||
|
|
789
|
+
body.data.rateLimit !== undefined)
|
|
809
790
|
return Response.json({ error: "Invalid key name" }, { status: 400 });
|
|
810
791
|
const owner = context.get("principal");
|
|
811
792
|
if (!oysterPrincipalIsOwner(owner))
|
|
812
793
|
return Response.json({ error: "Owner only" }, { status: 403 });
|
|
813
|
-
const grants = oysterGrantsArraySchema.safeParse(body.value.grants);
|
|
814
|
-
if (!grants.success)
|
|
815
|
-
return Response.json({ error: "Invalid grants" }, { status: 400 });
|
|
816
794
|
const issued = await keys().issue(owner.actor.memberId, {
|
|
817
|
-
name: body.
|
|
818
|
-
grants:
|
|
819
|
-
expiresAt: body.
|
|
795
|
+
name: body.data.name,
|
|
796
|
+
grants: body.data.grants,
|
|
797
|
+
expiresAt: body.data.expiresAt,
|
|
820
798
|
});
|
|
821
799
|
if (issued.kind === "error")
|
|
822
800
|
return Response.json({ error: issued.error.code }, { status: issued.error.code === "conflict" ? 409 : 400 });
|
|
823
|
-
return Response.json({
|
|
801
|
+
return Response.json({
|
|
802
|
+
plaintext: issued.value.key,
|
|
803
|
+
key: keysWire.toSummary(issued.value.record),
|
|
804
|
+
}, { status: 201 });
|
|
824
805
|
});
|
|
825
806
|
routes.post("/api/keys/revoke", async (context) => {
|
|
826
|
-
const body =
|
|
827
|
-
if (body.
|
|
807
|
+
const body = keysWire.revoke.safeParse(await context.req.json());
|
|
808
|
+
if (!body.success)
|
|
828
809
|
return Response.json({ error: "Invalid request" }, { status: 400 });
|
|
829
810
|
const owner = context.get("principal");
|
|
830
811
|
if (!oysterPrincipalIsOwner(owner))
|
|
831
812
|
return Response.json({ error: "Owner only" }, { status: 403 });
|
|
832
|
-
const revoked = await keys().revoke(owner.actor.memberId, body.
|
|
813
|
+
const revoked = await keys().revoke(owner.actor.memberId, body.data.requestId, body.data.keyId);
|
|
833
814
|
if (revoked.kind === "error")
|
|
834
815
|
return Response.json({ error: revoked.error.code }, { status: revoked.error.code === "denied" ? 404 : 400 });
|
|
835
|
-
return Response.json({ key:
|
|
816
|
+
return Response.json({ key: keysWire.toSummary(revoked.value) });
|
|
836
817
|
});
|
|
837
818
|
routes.post("/api/webhooks", async (context) => {
|
|
838
819
|
const body = Schema.decodeUnknownOption(subscribeSchema)(await context.req.json());
|
|
@@ -864,7 +845,11 @@ export function oysterGitRoot(options) {
|
|
|
864
845
|
const stateOf = (env) => {
|
|
865
846
|
let state = environments.get(env);
|
|
866
847
|
if (state === undefined) {
|
|
867
|
-
state = {
|
|
848
|
+
state = {
|
|
849
|
+
tenancy: options.tenancy(env),
|
|
850
|
+
admissions: new WeakMap(),
|
|
851
|
+
authentications: new WeakMap(),
|
|
852
|
+
};
|
|
868
853
|
environments.set(env, state);
|
|
869
854
|
}
|
|
870
855
|
return state;
|
|
@@ -877,12 +862,12 @@ export function oysterGitRoot(options) {
|
|
|
877
862
|
state.admissions.set(request, resolved);
|
|
878
863
|
return resolved;
|
|
879
864
|
},
|
|
880
|
-
hostFor(env, scope) {
|
|
865
|
+
hostFor(env, scope, request) {
|
|
881
866
|
const state = stateOf(env);
|
|
882
867
|
return oysterGitWorkerHost(env, options, {
|
|
883
868
|
scope,
|
|
884
|
-
auth: pinnedTenantAuth(state.tenancy, scope, state.admissions),
|
|
885
|
-
});
|
|
869
|
+
auth: pinnedTenantAuth(state.tenancy, scope, state.admissions, state.authentications),
|
|
870
|
+
}, request);
|
|
886
871
|
},
|
|
887
872
|
};
|
|
888
873
|
}
|
|
@@ -907,7 +892,19 @@ export function createOysterGitWorker(root) {
|
|
|
907
892
|
if (resolved === null)
|
|
908
893
|
return new Response("Not found", { status: 404 });
|
|
909
894
|
const { scope } = resolved;
|
|
910
|
-
const
|
|
895
|
+
const boot = await serveOysterBootCapability(request, env, scope);
|
|
896
|
+
if (boot !== null)
|
|
897
|
+
return boot;
|
|
898
|
+
let host;
|
|
899
|
+
try {
|
|
900
|
+
host = await root.hostFor(env, scope, request);
|
|
901
|
+
}
|
|
902
|
+
catch (error) {
|
|
903
|
+
if (error instanceof HTTPException)
|
|
904
|
+
return error.getResponse();
|
|
905
|
+
throw error;
|
|
906
|
+
}
|
|
907
|
+
const key = `${scope.tenantId}:${scope.origin}:${host.computerId ?? ""}`;
|
|
911
908
|
let entry = hosts.get(key);
|
|
912
909
|
let cache = true;
|
|
913
910
|
if (entry === undefined) {
|
|
@@ -919,7 +916,7 @@ export function createOysterGitWorker(root) {
|
|
|
919
916
|
hosts.delete(idle[0]);
|
|
920
917
|
}
|
|
921
918
|
}
|
|
922
|
-
entry = { app: createOyster(
|
|
919
|
+
entry = { app: createOyster(host), active: 0 };
|
|
923
920
|
if (cache)
|
|
924
921
|
hosts.set(key, entry);
|
|
925
922
|
}
|
|
@@ -969,7 +966,7 @@ export function oysterWorkerGit(env, options, frame) {
|
|
|
969
966
|
if (selected.owner.kind !== "installation" ||
|
|
970
967
|
selected.owner.id !== current.installationId ||
|
|
971
968
|
selected.instanceId !== current.instanceId ||
|
|
972
|
-
selected.authorityVersion !== current.authorityVersion ||
|
|
969
|
+
selected.authorityVersion !== current.authority.authorityVersion ||
|
|
973
970
|
selected.appId !== current.release.appId ||
|
|
974
971
|
selected.releaseId !== current.release.releaseId ||
|
|
975
972
|
captured.release.appId !== current.release.appId ||
|
|
@@ -990,7 +987,8 @@ export function oysterWorkerGit(env, options, frame) {
|
|
|
990
987
|
beforeProvider();
|
|
991
988
|
if (current?.status !== "enabled" ||
|
|
992
989
|
current.instanceId !== installation.instanceId ||
|
|
993
|
-
current.authorityVersion !==
|
|
990
|
+
current.authority.authorityVersion !==
|
|
991
|
+
installation.authority.authorityVersion ||
|
|
994
992
|
current.release.appId !== installation.release.appId ||
|
|
995
993
|
current.release.releaseId !== installation.release.releaseId ||
|
|
996
994
|
current.release.artifact.artifactRef !==
|
|
@@ -1072,7 +1070,7 @@ export function oysterWorkerGit(env, options, frame) {
|
|
|
1072
1070
|
* One selected tenant's whole host from a root's options. Roots reach it through
|
|
1073
1071
|
* `oysterGitRoot(options).hostFor`; a cold directory passes its own resolved tenant.
|
|
1074
1072
|
*/
|
|
1075
|
-
export function oysterGitWorkerHost(env, options, selected) {
|
|
1073
|
+
export async function oysterGitWorkerHost(env, options, selected, request) {
|
|
1076
1074
|
const { scope, auth } = selected;
|
|
1077
1075
|
// The one path to a key's liveness: the stored owner id is re-checked
|
|
1078
1076
|
// against the live, current owner on every read (S3 inherits this port,
|
|
@@ -1090,7 +1088,7 @@ export function oysterGitWorkerHost(env, options, selected) {
|
|
|
1090
1088
|
});
|
|
1091
1089
|
return current !== null && oysterPrincipalIsOwner(current) ? key : null;
|
|
1092
1090
|
};
|
|
1093
|
-
const workspace = options.workspace?.(env, keysLive) ?? {
|
|
1091
|
+
const workspace = (await options.workspace?.(env, keysLive, scope, request === undefined ? undefined : { request, auth })) ?? {
|
|
1094
1092
|
computerId: scope.computerName("workspace"),
|
|
1095
1093
|
files: r2FileSource(env.RELEASES, scope.workspacePrefix),
|
|
1096
1094
|
};
|
|
@@ -1133,6 +1131,8 @@ export function oysterGitWorkerHost(env, options, selected) {
|
|
|
1133
1131
|
};
|
|
1134
1132
|
if (options.pageScript !== undefined)
|
|
1135
1133
|
host = { ...host, pageScript: options.pageScript };
|
|
1134
|
+
if (options.workflows !== undefined)
|
|
1135
|
+
host = { ...host, workflows: options.workflows(env, host) };
|
|
1136
1136
|
if (options.homeInstallationId !== undefined)
|
|
1137
1137
|
host = { ...host, homeInstallationId: options.homeInstallationId(env) };
|
|
1138
1138
|
return host;
|
|
@@ -1141,7 +1141,7 @@ export function oysterGitWorkerHost(env, options, selected) {
|
|
|
1141
1141
|
* Authenticate against the pinned tenant. A request admitted at the Worker's
|
|
1142
1142
|
* top reuses that one admission; any other entry resolves its own.
|
|
1143
1143
|
*/
|
|
1144
|
-
function pinnedTenantAuth(tenancy, scope, admissions) {
|
|
1144
|
+
function pinnedTenantAuth(tenancy, scope, admissions, authentications) {
|
|
1145
1145
|
const admitted = async (incoming) => admissions.get(incoming) ?? (await tenancy.resolve(incoming));
|
|
1146
1146
|
return {
|
|
1147
1147
|
resolve: async (actor) => {
|
|
@@ -1156,7 +1156,12 @@ function pinnedTenantAuth(tenancy, scope, admissions) {
|
|
|
1156
1156
|
if (current?.scope.tenantId !== scope.tenantId ||
|
|
1157
1157
|
current.scope.origin !== scope.origin)
|
|
1158
1158
|
return { kind: "refused", status: 404 };
|
|
1159
|
-
|
|
1159
|
+
let authenticated = authentications.get(incoming);
|
|
1160
|
+
if (authenticated === undefined) {
|
|
1161
|
+
authenticated = current.auth.authenticate(incoming);
|
|
1162
|
+
authentications.set(incoming, authenticated);
|
|
1163
|
+
}
|
|
1164
|
+
return authenticated;
|
|
1160
1165
|
},
|
|
1161
1166
|
login: async (incoming) => {
|
|
1162
1167
|
const current = await admitted(incoming);
|