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