@fungi.computer/oyster 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +1134 -2
- package/dist/agents.d.ts +114 -0
- package/dist/agents.js +322 -0
- package/dist/app-builds.d.ts +210 -0
- package/dist/app-builds.js +499 -0
- package/dist/app-sandbox.d.ts +2 -0
- package/dist/app-sandbox.js +2 -0
- package/dist/app-sources.d.ts +31 -0
- package/dist/app-sources.js +370 -0
- package/dist/app-storage.d.ts +141 -0
- package/dist/app-storage.js +281 -0
- package/dist/apps/gate.d.ts +105 -0
- package/dist/apps/gate.js +273 -0
- package/dist/apps/index.d.ts +5 -0
- package/dist/apps/index.js +5 -0
- package/dist/apps/installation.d.ts +248 -0
- package/dist/apps/installation.js +307 -0
- package/dist/apps/operations.d.ts +8 -0
- package/dist/apps/operations.js +98 -0
- package/dist/apps/tools.d.ts +33 -0
- package/dist/apps/tools.js +17 -0
- package/dist/auth.d.ts +11 -0
- package/dist/auth.js +93 -0
- package/dist/backend-request.d.ts +3 -0
- package/dist/backend-request.js +53 -0
- package/dist/build/artifact-writer.d.ts +40 -0
- package/dist/build/artifact-writer.js +181 -0
- package/dist/build/build-jobs.d.ts +128 -0
- package/dist/build/build-jobs.js +203 -0
- package/dist/build/build-policy.d.ts +10 -0
- package/dist/build/build-policy.js +10 -0
- package/dist/build/build-step.d.ts +37 -0
- package/dist/build/build-step.js +28 -0
- package/dist/build/bundled-html.d.ts +7 -0
- package/dist/build/bundled-html.js +64 -0
- package/dist/build/candidate-builder.d.ts +68 -0
- package/dist/build/candidate-builder.js +91 -0
- package/dist/build/candidate-cli.d.ts +1 -0
- package/dist/build/candidate-cli.js +119 -0
- package/dist/build/candidate-cli.mjs +28170 -0
- package/dist/build/content-objects.d.ts +37 -0
- package/dist/build/content-objects.js +53 -0
- package/dist/build/definition-emission.d.ts +34 -0
- package/dist/build/definition-emission.js +201 -0
- package/dist/build/effect.d.ts +249 -0
- package/dist/build/effect.js +436 -0
- package/dist/build/git-environment.d.ts +9 -0
- package/dist/build/git-environment.js +60 -0
- package/dist/build/html-recipe.d.ts +20 -0
- package/dist/build/html-recipe.js +51 -0
- package/dist/build/local-build.d.ts +67 -0
- package/dist/build/local-build.js +1128 -0
- package/dist/build/snapshot-package-store.d.ts +12 -0
- package/dist/build/snapshot-package-store.js +36 -0
- package/dist/build/snapshot-program-scripts.d.ts +6 -0
- package/dist/build/snapshot-program-scripts.js +2 -0
- package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
- package/dist/build/snapshot-programs/snapshot-path.js +6 -0
- package/dist/build/submission-contract.d.ts +828 -0
- package/dist/build/submission-contract.js +180 -0
- package/dist/build/workspace-build.d.ts +55 -0
- package/dist/build/workspace-build.js +200 -0
- package/dist/build-contract.d.ts +7 -0
- package/dist/build-contract.js +7 -0
- package/dist/build-egress.d.ts +62 -0
- package/dist/build-egress.js +148 -0
- package/dist/build-jobs.d.ts +2 -0
- package/dist/build-jobs.js +2 -0
- package/dist/build.d.ts +12 -0
- package/dist/build.js +14 -0
- package/dist/capability-descriptor.d.ts +46 -0
- package/dist/capability-descriptor.js +51 -0
- package/dist/catalog-frame.d.ts +39 -0
- package/dist/catalog-frame.js +72 -0
- package/dist/cause-class.d.ts +19 -0
- package/dist/cause-class.js +54 -0
- package/dist/cloudflare.d.ts +123 -0
- package/dist/cloudflare.js +391 -0
- package/dist/computer-terminal.d.ts +38 -0
- package/dist/computer-terminal.js +410 -0
- package/dist/computer.d.ts +60 -0
- package/dist/computer.js +33 -0
- package/dist/event-recipients.d.ts +57 -0
- package/dist/event-recipients.js +144 -0
- package/dist/facet-invocation.d.ts +4 -0
- package/dist/facet-invocation.js +45 -0
- package/dist/files.d.ts +42 -0
- package/dist/files.js +123 -0
- package/dist/frame-binding-wire-script.d.ts +2 -0
- package/dist/frame-binding-wire-script.js +1 -0
- package/dist/frame-binding-wire.d.ts +28 -0
- package/dist/frame-binding-wire.js +54 -0
- package/dist/git-admission.d.ts +52 -0
- package/dist/git-admission.js +188 -0
- package/dist/git-auth.d.ts +11 -0
- package/dist/git-auth.js +27 -0
- package/dist/git-door.d.ts +35 -0
- package/dist/git-door.js +108 -0
- package/dist/git-journal.d.ts +31 -0
- package/dist/git-journal.js +188 -0
- package/dist/git.d.ts +31 -0
- package/dist/git.js +64 -0
- package/dist/guest-policy.d.ts +9 -0
- package/dist/guest-policy.js +13 -0
- package/dist/host.d.ts +244 -0
- package/dist/host.js +1043 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +320 -0
- package/dist/installation-consent.d.ts +79 -0
- package/dist/installation-consent.js +82 -0
- package/dist/internal/background-origins.d.ts +3 -0
- package/dist/internal/background-origins.js +8 -0
- package/dist/internal/delivery.d.ts +12 -0
- package/dist/internal/delivery.js +167 -0
- package/dist/internal/host-gate.d.ts +14 -0
- package/dist/internal/host-gate.js +62 -0
- package/dist/internal/registry-installations.d.ts +6 -0
- package/dist/internal/registry-installations.js +12 -0
- package/dist/invocation.d.ts +251 -0
- package/dist/invocation.js +377 -0
- package/dist/keys.d.ts +48 -0
- package/dist/keys.js +52 -0
- package/dist/mcp-owner.d.ts +27 -0
- package/dist/mcp-owner.js +155 -0
- package/dist/mcp.d.ts +89 -0
- package/dist/mcp.js +429 -0
- package/dist/media.d.ts +23 -0
- package/dist/media.js +12 -0
- package/dist/pack.d.ts +79 -0
- package/dist/pack.js +151 -0
- package/dist/page/capabilities.d.ts +23 -0
- package/dist/page/capabilities.js +256 -0
- package/dist/page/computer-handles.d.ts +79 -0
- package/dist/page/computer-handles.js +44 -0
- package/dist/page/gill-observation.d.ts +4 -0
- package/dist/page/gill-observation.js +31 -0
- package/dist/page/lifetime.d.ts +6 -0
- package/dist/page/lifetime.js +19 -0
- package/dist/page/mount.d.ts +163 -0
- package/dist/page/mount.js +390 -0
- package/dist/page/storage.d.ts +3 -0
- package/dist/page/storage.js +82 -0
- package/dist/page/terminal.d.ts +5 -0
- package/dist/page/terminal.js +7 -0
- package/dist/page/window-launch.d.ts +18 -0
- package/dist/page/window-launch.js +11 -0
- package/dist/page-script.d.ts +14 -0
- package/dist/page-script.js +5 -0
- package/dist/r2-files.d.ts +3 -0
- package/dist/r2-files.js +79 -0
- package/dist/release.d.ts +83 -0
- package/dist/release.js +167 -0
- package/dist/root-data.d.ts +86 -0
- package/dist/root-data.js +69 -0
- package/dist/shell/app-directory.d.ts +542 -0
- package/dist/shell/app-directory.js +199 -0
- package/dist/shell/browser-actions.d.ts +7 -0
- package/dist/shell/browser-actions.js +56 -0
- package/dist/shell/command-host.d.ts +112 -0
- package/dist/shell/command-host.js +502 -0
- package/dist/shell/commands.d.ts +25 -0
- package/dist/shell/commands.js +78 -0
- package/dist/shell/index.d.ts +8 -0
- package/dist/shell/index.js +8 -0
- package/dist/shell/keys.d.ts +41 -0
- package/dist/shell/keys.js +134 -0
- package/dist/shell/shell-module.d.ts +55 -0
- package/dist/shell/shell-module.js +86 -0
- package/dist/shell/window-manager.d.ts +124 -0
- package/dist/shell/window-manager.js +711 -0
- package/dist/shell/workbench-contract.d.ts +68 -0
- package/dist/shell/workbench-contract.js +1 -0
- package/dist/shell/workbench-directory.d.ts +332 -0
- package/dist/shell/workbench-directory.js +35 -0
- package/dist/shell/workbench-host-types.d.ts +96 -0
- package/dist/shell/workbench-host-types.js +1 -0
- package/dist/shell/workbench-host.d.ts +34 -0
- package/dist/shell/workbench-host.js +347 -0
- package/dist/shell/workbench-model.d.ts +192 -0
- package/dist/shell/workbench-model.js +53 -0
- package/dist/shell/workbench.d.ts +4 -0
- package/dist/shell/workbench.js +2 -0
- package/dist/source/fork.d.ts +27 -0
- package/dist/source/fork.js +46 -0
- package/dist/source/index.d.ts +10 -0
- package/dist/source/index.js +10 -0
- package/dist/source/seed-receipt.d.ts +53 -0
- package/dist/source/seed-receipt.js +18 -0
- package/dist/source/team-source.d.ts +32 -0
- package/dist/source/team-source.js +42 -0
- package/dist/source/template-push.d.ts +33 -0
- package/dist/source/template-push.js +223 -0
- package/dist/source/template-repository.d.ts +4 -0
- package/dist/source/template-repository.js +21 -0
- package/dist/source/template-seed.d.ts +68 -0
- package/dist/source/template-seed.js +80 -0
- package/dist/standalone/agent-directory.d.ts +4 -0
- package/dist/standalone/agent-directory.js +4 -0
- package/dist/standalone/agents.d.ts +220 -0
- package/dist/standalone/agents.js +795 -0
- package/dist/standalone/app-builds-binding.d.ts +36 -0
- package/dist/standalone/app-builds-binding.js +119 -0
- package/dist/standalone/app-set.d.ts +91 -0
- package/dist/standalone/app-set.js +395 -0
- package/dist/standalone/app-source.d.ts +41 -0
- package/dist/standalone/app-source.js +59 -0
- package/dist/standalone/builds.d.ts +18 -0
- package/dist/standalone/builds.js +101 -0
- package/dist/standalone/computer-egress.d.ts +37 -0
- package/dist/standalone/computer-egress.js +111 -0
- package/dist/standalone/computer.d.ts +122 -0
- package/dist/standalone/computer.js +714 -0
- package/dist/standalone/install-apps-cli.d.ts +2 -0
- package/dist/standalone/install-apps-cli.js +126 -0
- package/dist/standalone/instance.d.ts +35 -0
- package/dist/standalone/instance.js +74 -0
- package/dist/standalone/media.d.ts +76 -0
- package/dist/standalone/media.js +227 -0
- package/dist/standalone/preview.d.ts +51 -0
- package/dist/standalone/preview.js +167 -0
- package/dist/standalone/registry.d.ts +102 -0
- package/dist/standalone/registry.js +193 -0
- package/dist/standalone/tenancy.d.ts +42 -0
- package/dist/standalone/tenancy.js +241 -0
- package/dist/standalone/worker.d.ts +55 -0
- package/dist/standalone/worker.js +147 -0
- package/dist/standalone/workflow-authority-readers.d.ts +16 -0
- package/dist/standalone/workflow-authority-readers.js +35 -0
- package/dist/standalone/workflow-binding.d.ts +216 -0
- package/dist/standalone/workflow-binding.js +217 -0
- package/dist/standalone/workflow-events.d.ts +46 -0
- package/dist/standalone/workflow-events.js +39 -0
- package/dist/standalone/workflow-git-capture.d.ts +7 -0
- package/dist/standalone/workflow-git-capture.js +62 -0
- package/dist/standalone/workflow-host.d.ts +931 -0
- package/dist/standalone/workflow-host.js +751 -0
- package/dist/standalone/workflow-node-capture.d.ts +13 -0
- package/dist/standalone/workflow-node-capture.js +72 -0
- package/dist/standalone/workflow-root-authority.d.ts +27 -0
- package/dist/standalone/workflow-root-authority.js +87 -0
- package/dist/standalone/workflow-schedules.d.ts +52 -0
- package/dist/standalone/workflow-schedules.js +40 -0
- package/dist/standalone/workflow-trigger-host.d.ts +1694 -0
- package/dist/standalone/workflow-trigger-host.js +52 -0
- package/dist/standalone/workflows.d.ts +657 -0
- package/dist/standalone/workflows.js +554 -0
- package/dist/storage-frame.d.ts +64 -0
- package/dist/storage-frame.js +28 -0
- package/dist/tenancy/auth0.d.ts +64 -0
- package/dist/tenancy/auth0.js +198 -0
- package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
- package/dist/tenancy/cloudflare/rate-limit.js +19 -0
- package/dist/tenancy/cloudflare/registry.d.ts +50 -0
- package/dist/tenancy/cloudflare/registry.js +301 -0
- package/dist/tenancy/cloudflare.d.ts +2 -0
- package/dist/tenancy/cloudflare.js +2 -0
- package/dist/tenancy/cold-directory.d.ts +13 -0
- package/dist/tenancy/cold-directory.js +43 -0
- package/dist/tenancy/contracts.d.ts +195 -0
- package/dist/tenancy/contracts.js +1 -0
- package/dist/tenancy/index.d.ts +19 -0
- package/dist/tenancy/index.js +156 -0
- package/dist/tenancy/owner-key.d.ts +26 -0
- package/dist/tenancy/owner-key.js +63 -0
- package/dist/tenancy/oyster.d.ts +4 -0
- package/dist/tenancy/oyster.js +25 -0
- package/dist/tenancy/registry-record.d.ts +7 -0
- package/dist/tenancy/registry-record.js +44 -0
- package/dist/tenancy/registry.d.ts +27 -0
- package/dist/tenancy/registry.js +138 -0
- package/dist/tenancy/resolve.d.ts +7 -0
- package/dist/tenancy/resolve.js +64 -0
- package/dist/tenant.d.ts +81 -0
- package/dist/tenant.js +71 -0
- package/dist/testing/capabilities.d.ts +29 -0
- package/dist/testing/capabilities.js +130 -0
- package/dist/testing/catalog.d.ts +3 -0
- package/dist/testing/catalog.js +53 -0
- package/dist/testing/index.d.ts +10 -0
- package/dist/testing/index.js +10 -0
- package/dist/testing/installation.d.ts +11 -0
- package/dist/testing/installation.js +25 -0
- package/dist/testing/native-worker.d.ts +21 -0
- package/dist/testing/native-worker.js +106 -0
- package/dist/testing/worker.d.ts +18 -0
- package/dist/testing/worker.js +67 -0
- package/dist/testing/workers-runtime.d.ts +24 -0
- package/dist/testing/workers-runtime.js +33 -0
- package/dist/webhook-delivery.d.ts +14 -0
- package/dist/webhook-delivery.js +17 -0
- package/dist/workers-git.d.ts +285 -0
- package/dist/workers-git.js +1174 -0
- package/dist/workers.d.ts +352 -0
- package/dist/workers.js +868 -0
- package/docs/api/README.md +82 -0
- package/docs/api/agents.md +332 -0
- package/docs/api/app-storage.md +209 -0
- package/docs/api/apps.md +1375 -0
- package/docs/api/auth.md +63 -0
- package/docs/api/build-contract.md +1304 -0
- package/docs/api/build.md +1712 -0
- package/docs/api/cloudflare.md +421 -0
- package/docs/api/computer.md +203 -0
- package/docs/api/files.md +117 -0
- package/docs/api/git.md +470 -0
- package/docs/api/host.md +1328 -0
- package/docs/api/index.md +430 -0
- package/docs/api/invocation.md +738 -0
- package/docs/api/keys.md +175 -0
- package/docs/api/mcp.md +301 -0
- package/docs/api/media.md +40 -0
- package/docs/api/modules.md +50 -0
- package/docs/api/pack.md +141 -0
- package/docs/api/page/mount.md +298 -0
- package/docs/api/root-data.md +111 -0
- package/docs/api/shell/app-directory.md +599 -0
- package/docs/api/shell/command-host.md +224 -0
- package/docs/api/shell/commands.md +197 -0
- package/docs/api/shell/keys.md +183 -0
- package/docs/api/shell/shell-module.md +204 -0
- package/docs/api/shell/window-manager.md +267 -0
- package/docs/api/shell/workbench-directory.md +119 -0
- package/docs/api/shell/workbench.md +205 -0
- package/docs/api/shell.md +1915 -0
- package/docs/api/tenancy/auth0.md +110 -0
- package/docs/api/tenancy/cloudflare.md +221 -0
- package/docs/api/tenancy/cold-directory.md +46 -0
- package/docs/api/tenancy/owner-key.md +63 -0
- package/docs/api/tenancy/registry.md +77 -0
- package/docs/api/tenancy/resolve.md +69 -0
- package/docs/api/tenancy.md +430 -0
- package/docs/api/tenant.md +194 -0
- package/docs/api/workers-git.md +1646 -0
- package/docs/api/workers.md +1691 -0
- package/docs/site/agent-quickstart.mdx +346 -0
- package/examples/standalone/worker.ts +7 -0
- package/examples/standalone/wrangler.jsonc +82 -0
- package/package.json +300 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { ComputerPortPreview } from "@fungi.computer/computer/preview";
|
|
4
|
+
import { selectionSchema } from "../index.js";
|
|
5
|
+
import { sha256DigestSchema } from "@fungi.computer/puffball/effect";
|
|
6
|
+
import { computerPortInputSchema } from "@fungi.computer/computer/operations";
|
|
7
|
+
export const previewSelectorSchema = Schema.Struct({
|
|
8
|
+
ownerId: Schema.NonEmptyString,
|
|
9
|
+
installationId: Schema.NonEmptyString,
|
|
10
|
+
selection: selectionSchema,
|
|
11
|
+
artifactDigest: sha256DigestSchema,
|
|
12
|
+
});
|
|
13
|
+
const originSchema = z
|
|
14
|
+
.url()
|
|
15
|
+
.transform((input) => new URL(input))
|
|
16
|
+
.refine((url) => url.protocol === "https:" &&
|
|
17
|
+
!url.username &&
|
|
18
|
+
!url.password &&
|
|
19
|
+
url.pathname === "/" &&
|
|
20
|
+
!url.search &&
|
|
21
|
+
!url.hash);
|
|
22
|
+
export function previewOrigins(env) {
|
|
23
|
+
if (env.OYSTER_PREVIEW_ORIGIN === undefined)
|
|
24
|
+
return null;
|
|
25
|
+
const parsed = z
|
|
26
|
+
.object({ owner: originSchema, preview: originSchema })
|
|
27
|
+
.safeParse({
|
|
28
|
+
owner: env.OYSTER_ORIGIN,
|
|
29
|
+
preview: env.OYSTER_PREVIEW_ORIGIN,
|
|
30
|
+
});
|
|
31
|
+
if (!parsed.success)
|
|
32
|
+
return null;
|
|
33
|
+
const { owner, preview } = parsed.data;
|
|
34
|
+
// The preview tunnel must be a distinct origin from the canonical owner
|
|
35
|
+
// origin: it carries no cookies and no owner credential, so it cannot be
|
|
36
|
+
// the same origin the owner signs in on. Which two origins those are is a
|
|
37
|
+
// deploy-time choice (this Worker's own custom domain plus its workers.dev
|
|
38
|
+
// address, or any other pair the operator actually controls) — never a
|
|
39
|
+
// literal this module hard-codes.
|
|
40
|
+
if (owner.origin === preview.origin)
|
|
41
|
+
return null;
|
|
42
|
+
return { owner: owner.origin, preview: preview.origin };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The configured preview tunnel origin alone, independent of whether the
|
|
46
|
+
* owner origin also validates. Once an operator points OYSTER_PREVIEW_ORIGIN
|
|
47
|
+
* at a host, that host is always treated as preview-only and never falls
|
|
48
|
+
* through to the normal router — even if the rest of the config (the owner
|
|
49
|
+
* origin) is separately broken. Only a host nothing configured as preview
|
|
50
|
+
* is left to the caller's own routing.
|
|
51
|
+
*/
|
|
52
|
+
function configuredPreviewOrigin(env) {
|
|
53
|
+
const parsed = originSchema.safeParse(env.OYSTER_PREVIEW_ORIGIN);
|
|
54
|
+
return parsed.success ? parsed.data.origin : null;
|
|
55
|
+
}
|
|
56
|
+
export class PreviewRefused extends Error {
|
|
57
|
+
}
|
|
58
|
+
export function createStandalonePreview(storage, computerId, origin, native) {
|
|
59
|
+
const tickets = new ComputerPortPreview(storage, {
|
|
60
|
+
selector: previewSelectorSchema,
|
|
61
|
+
previewUrl: (signature, base) => `${base}/${signature}/`,
|
|
62
|
+
accept: native.accept,
|
|
63
|
+
listPorts: native.list,
|
|
64
|
+
forward: native.forward,
|
|
65
|
+
portDenied: () => {
|
|
66
|
+
throw new PreviewRefused();
|
|
67
|
+
},
|
|
68
|
+
dispatchFailure: (cause) => {
|
|
69
|
+
if (cause instanceof PreviewRefused)
|
|
70
|
+
return new Response(null, { status: 403 });
|
|
71
|
+
console.error("Standalone preview native dispatch failed", cause);
|
|
72
|
+
return new Response(null, { status: 502 });
|
|
73
|
+
},
|
|
74
|
+
invalidClaims: () => {
|
|
75
|
+
console.error("Standalone preview ticket claims refused");
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
async issue(selector, port) {
|
|
80
|
+
await native.current(selector);
|
|
81
|
+
return tickets.issueAfterAcceptance(selector, port, `${origin}/preview/${encodeURIComponent(computerId)}/${String(port)}`);
|
|
82
|
+
},
|
|
83
|
+
async fetch(signature, port, request) {
|
|
84
|
+
return tickets.dispatchSigned(signature, request, async (selector, selectedPort, incoming) => {
|
|
85
|
+
if (selectedPort !== port)
|
|
86
|
+
return new Response(null, { status: 403 });
|
|
87
|
+
try {
|
|
88
|
+
await native.current(selector);
|
|
89
|
+
return await passivePreviewResponse(await tickets.dispatch(selector, port, incoming));
|
|
90
|
+
}
|
|
91
|
+
catch (cause) {
|
|
92
|
+
if (cause instanceof PreviewRefused)
|
|
93
|
+
return new Response(null, { status: 403 });
|
|
94
|
+
console.error("Standalone preview current authority check failed", cause);
|
|
95
|
+
return new Response(null, { status: 503 });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export async function passivePreviewResponse(response) {
|
|
102
|
+
if (response.status < 200 ||
|
|
103
|
+
response.status >= 300 ||
|
|
104
|
+
response.headers.has("Refresh")) {
|
|
105
|
+
await response.body?.cancel();
|
|
106
|
+
return new Response(null, {
|
|
107
|
+
status: response.headers.has("Refresh") ||
|
|
108
|
+
(response.status >= 300 && response.status < 400)
|
|
109
|
+
? 502
|
|
110
|
+
: response.status,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const contentType = response.headers
|
|
114
|
+
.get("Content-Type")
|
|
115
|
+
?.split(";")[0]
|
|
116
|
+
?.trim()
|
|
117
|
+
.toLowerCase();
|
|
118
|
+
if (contentType !== "text/html" && contentType !== "text/plain") {
|
|
119
|
+
await response.body?.cancel();
|
|
120
|
+
return new Response(null, { status: 415 });
|
|
121
|
+
}
|
|
122
|
+
const headers = new Headers({
|
|
123
|
+
"Content-Type": `${contentType}; charset=utf-8`,
|
|
124
|
+
"Content-Security-Policy": "sandbox; default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'",
|
|
125
|
+
"Cache-Control": "no-store",
|
|
126
|
+
"X-Robots-Tag": "noindex, nofollow",
|
|
127
|
+
"Referrer-Policy": "no-referrer",
|
|
128
|
+
"X-Content-Type-Options": "nosniff",
|
|
129
|
+
});
|
|
130
|
+
return new Response(response.body, { status: response.status, headers });
|
|
131
|
+
}
|
|
132
|
+
export async function standalonePreviewRequest(request, env, computerId, forward) {
|
|
133
|
+
const incoming = new URL(request.url);
|
|
134
|
+
const previewHost = configuredPreviewOrigin(env);
|
|
135
|
+
// A host nothing configured as the preview tunnel is left to the caller's
|
|
136
|
+
// own routing, like any other Host this module doesn't recognize.
|
|
137
|
+
if (previewHost === null || incoming.origin !== previewHost)
|
|
138
|
+
return null;
|
|
139
|
+
// Past this point the Host is the configured preview tunnel: it never
|
|
140
|
+
// falls through to the normal router, even if the rest of the pair (the
|
|
141
|
+
// owner origin) turns out to be broken — the checks below just decide
|
|
142
|
+
// 403/404/503 vs. forwarding.
|
|
143
|
+
if (!["GET", "HEAD"].includes(request.method) ||
|
|
144
|
+
request.headers.has("Upgrade"))
|
|
145
|
+
return new Response(null, { status: 404 });
|
|
146
|
+
const path = /^\/preview\/([^/]+)\/([0-9]{1,5})\/([a-z2-7]{20})(\/.*)$/u.exec(incoming.pathname);
|
|
147
|
+
if (path?.[1] !== encodeURIComponent(computerId) ||
|
|
148
|
+
path[3] === undefined ||
|
|
149
|
+
path[4] === undefined)
|
|
150
|
+
return new Response(null, { status: 404 });
|
|
151
|
+
if (previewOrigins(env) === null)
|
|
152
|
+
return new Response(null, { status: 404 });
|
|
153
|
+
const input = computerPortInputSchema.safeParse({ port: Number(path[2]) });
|
|
154
|
+
if (!input.success || String(input.data.port) !== path[2])
|
|
155
|
+
return new Response(null, { status: 404 });
|
|
156
|
+
const service = new URL("http://localhost");
|
|
157
|
+
service.pathname = path[4];
|
|
158
|
+
service.search = incoming.search;
|
|
159
|
+
const headers = new Headers(request.headers);
|
|
160
|
+
for (const name of ["cookie", "authorization", "proxy-authorization", "host"])
|
|
161
|
+
headers.delete(name);
|
|
162
|
+
return forward(path[3], input.data.port, new Request(service, {
|
|
163
|
+
method: request.method,
|
|
164
|
+
headers,
|
|
165
|
+
signal: request.signal,
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { OysterRegistry, type OysterWorkerEnv } from "../workers.js";
|
|
2
|
+
import type { TenantId } from "../tenant.js";
|
|
3
|
+
import { type OysterGitEnv } from "../workers-git.js";
|
|
4
|
+
import { type AppSourceForkResult, type TemplateSeedClaim } from "../source/index.js";
|
|
5
|
+
import { type CloudflareArtifactsOptions } from "@fungi.computer/burrow/cloudflare";
|
|
6
|
+
import { type OysterBuildSource, type OysterBuildView } from "../app-builds.js";
|
|
7
|
+
import type { TeamAppCandidateManifest } from "../build-contract.js";
|
|
8
|
+
import type { BuildGitEvent } from "../build-egress.js";
|
|
9
|
+
import { type StandaloneTenancyEnv } from "./tenancy.js";
|
|
10
|
+
import type { ComputerDO } from "./computer.js";
|
|
11
|
+
/** What the registry needs to build Apps on the standalone's one Computer. */
|
|
12
|
+
export type StandaloneRegistryEnv = OysterWorkerEnv & StandaloneTenancyEnv & OysterGitEnv & Readonly<{
|
|
13
|
+
OYSTER_COMPUTER_ID: string;
|
|
14
|
+
COMPUTER: DurableObjectNamespace<ComputerDO>;
|
|
15
|
+
}>;
|
|
16
|
+
/** One repository a build clone may read, at one installation. */
|
|
17
|
+
export type BuildTarget = Readonly<{
|
|
18
|
+
installationId: string;
|
|
19
|
+
repository: string;
|
|
20
|
+
scope: "read" | "write";
|
|
21
|
+
}>;
|
|
22
|
+
/** The Git transports of the maintained Artifacts owner, which the host's
|
|
23
|
+
* composition may supply; absent, Git goes to the provider's own remotes. */
|
|
24
|
+
export type StandaloneRegistryGit = Pick<CloudflareArtifactsOptions, "gitFetch" | "gitHttp">;
|
|
25
|
+
/** The existing REGISTRY class identity with standalone host-owned admission. */
|
|
26
|
+
export declare class StandaloneRegistry extends OysterRegistry {
|
|
27
|
+
#private;
|
|
28
|
+
constructor(state: DurableObjectState, env: StandaloneRegistryEnv, git?: StandaloneRegistryGit);
|
|
29
|
+
/** One atomic registry admission proves the stored creator is still the owner. */
|
|
30
|
+
protected currentOwner(memberId: string, tenantId: TenantId): Promise<string | null>;
|
|
31
|
+
/** Operator-only: one smart-HTTP exchange with an App's template
|
|
32
|
+
* repository, the `git push` that seeds it. */
|
|
33
|
+
exchangeTemplateSource(rawAppId: string, suffix: string, request: Request): Promise<Response>;
|
|
34
|
+
/** Operator-only: record the seed the operator just pushed to this
|
|
35
|
+
* Oyster's editable App source template, register the platform App itself
|
|
36
|
+
* as this tenant's own publisher so a later `forkApp` can authorize
|
|
37
|
+
* against it (`fork.ts` reads this record), and make that template the
|
|
38
|
+
* App's build source with the recipe its seed receipt pins, so the
|
|
39
|
+
* published seed revision builds on the Computer. */
|
|
40
|
+
publishSource(rawAppId: string, claim: TemplateSeedClaim, teamId: string): Promise<{
|
|
41
|
+
readonly kind: "conflict";
|
|
42
|
+
readonly receipt?: undefined;
|
|
43
|
+
} | {
|
|
44
|
+
readonly kind: "recorded" | "existing";
|
|
45
|
+
readonly receipt: {
|
|
46
|
+
readonly appId: import("@fungi.computer/puffball").AppId;
|
|
47
|
+
readonly repository: string;
|
|
48
|
+
readonly sourceRevision: import("@fungi.computer/puffball").SourceRevision;
|
|
49
|
+
readonly sourceTreeDigest: import("@fungi.computer/puffball").Sha256Digest;
|
|
50
|
+
readonly seedRevision: import("@fungi.computer/puffball").SourceRevision;
|
|
51
|
+
readonly buildRecipeDigest: import("@fungi.computer/puffball").Sha256Digest;
|
|
52
|
+
};
|
|
53
|
+
} | {
|
|
54
|
+
readonly kind: "unavailable";
|
|
55
|
+
readonly receipt?: undefined;
|
|
56
|
+
}>;
|
|
57
|
+
/** Fork a published platform App's source into its own Team-owned App,
|
|
58
|
+
* as repository `name` of the calling installation's Git (so its owner
|
|
59
|
+
* edits and merges it there), and register that repository as the new
|
|
60
|
+
* App's build source with the template's pinned recipe, so a merged
|
|
61
|
+
* commit builds without further setup. */
|
|
62
|
+
forkApp(input: Readonly<{
|
|
63
|
+
teamId: string;
|
|
64
|
+
platformAppId: string;
|
|
65
|
+
/** The installation whose Git holds the fork, e.g. Bonsai. */
|
|
66
|
+
installationId: string;
|
|
67
|
+
/** That installation's provider repository prefix (`TenantScope`). */
|
|
68
|
+
repositoryPrefix: string;
|
|
69
|
+
name: string;
|
|
70
|
+
}>): Promise<AppSourceForkResult>;
|
|
71
|
+
/** Owner: record an App's build source (one repository, pinned recipe). */
|
|
72
|
+
registerBuildSource(source: OysterBuildSource): Promise<void>;
|
|
73
|
+
/** Owner: start one build of an App's source at one commit; it runs on. */
|
|
74
|
+
startBuild(input: {
|
|
75
|
+
appId: string;
|
|
76
|
+
sourceRevision: string;
|
|
77
|
+
requestedBy: string;
|
|
78
|
+
}): Promise<OysterBuildView>;
|
|
79
|
+
readBuild(jobId: string): Promise<OysterBuildView | null>;
|
|
80
|
+
/** The Computer re-admits each build execution here. */
|
|
81
|
+
admitBuildExecution(jobId: string, computerObjectId: string): Promise<boolean>;
|
|
82
|
+
/** The egress: which live job a build report from this Computer speaks for. */
|
|
83
|
+
admitBuildReport(jobId: string, computerObjectId: string): Promise<Readonly<{
|
|
84
|
+
credentialId: string;
|
|
85
|
+
}> | null>;
|
|
86
|
+
/** The egress: the clone pass for this Computer's live job on one repository. */
|
|
87
|
+
buildGitPassFor(computerObjectId: string, target: BuildTarget): Promise<string | null>;
|
|
88
|
+
/** The Git door: admit a presented build clone pass. */
|
|
89
|
+
admitBuildGitPass(pass: string, target: BuildTarget): Promise<Readonly<{
|
|
90
|
+
requestedBy: string;
|
|
91
|
+
}> | null>;
|
|
92
|
+
/** The egress: one read-only Git exchange with a template repository, for
|
|
93
|
+
* this Computer's live template build job only. */
|
|
94
|
+
exchangeBuildTemplate(computerObjectId: string, repository: string, request: Request): Promise<Response>;
|
|
95
|
+
recordBuildEvent(event: BuildGitEvent): Promise<void>;
|
|
96
|
+
/** The egress: may this Computer's live job upload one object, how large? */
|
|
97
|
+
admitBuildObject(jobId: string, computerObjectId: string, grant: string): Promise<Readonly<{
|
|
98
|
+
maxObjectBytes: number;
|
|
99
|
+
}> | null>;
|
|
100
|
+
/** The egress: a live job's candidate manifest; its objects are stored. */
|
|
101
|
+
submitBuildCandidate(jobId: string, manifest: TeamAppCandidateManifest, grant: string, credentialId: string): Promise<Response>;
|
|
102
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { OysterRegistry, r2ArtifactStore, } from "../workers.js";
|
|
2
|
+
import { artifactsOf } from "../workers-git.js";
|
|
3
|
+
import { forkAppSource, recordTemplateSeed, templateRepositoryForApp, } from "../source/index.js";
|
|
4
|
+
import { exchangeOysterTemplateGit, exchangeOysterTemplateSource, } from "../app-sources.js";
|
|
5
|
+
import { createCloudflareArtifacts, } from "@fungi.computer/burrow/cloudflare";
|
|
6
|
+
import { defineIdFamily } from "@fungi.computer/cairn/id";
|
|
7
|
+
import * as Schema from "effect/Schema";
|
|
8
|
+
import { parseAppId } from "@fungi.computer/puffball";
|
|
9
|
+
import { appIdSchema } from "@fungi.computer/puffball/effect";
|
|
10
|
+
import { createOysterAppBuilds, } from "../app-builds.js";
|
|
11
|
+
import { standaloneTenancy } from "./tenancy.js";
|
|
12
|
+
const standaloneAppForkIds = defineIdFamily({
|
|
13
|
+
family: "oyster-app-fork",
|
|
14
|
+
version: 1,
|
|
15
|
+
kinds: { app: { prefix: "app" } },
|
|
16
|
+
});
|
|
17
|
+
/** The existing REGISTRY class identity with standalone host-owned admission. */
|
|
18
|
+
export class StandaloneRegistry extends OysterRegistry {
|
|
19
|
+
#env;
|
|
20
|
+
#git;
|
|
21
|
+
#builds;
|
|
22
|
+
constructor(state, env, git = {}) {
|
|
23
|
+
super(state, env);
|
|
24
|
+
this.#env = env;
|
|
25
|
+
this.#git = git;
|
|
26
|
+
}
|
|
27
|
+
/** One atomic registry admission proves the stored creator is still the owner. */
|
|
28
|
+
async currentOwner(memberId, tenantId) {
|
|
29
|
+
const shared = await standaloneTenancy(this.#env);
|
|
30
|
+
if (tenantId !== shared.tenantId)
|
|
31
|
+
return null;
|
|
32
|
+
return (await shared.owner())?.actor.memberId === memberId
|
|
33
|
+
? memberId
|
|
34
|
+
: null;
|
|
35
|
+
}
|
|
36
|
+
#artifacts() {
|
|
37
|
+
return createCloudflareArtifacts({
|
|
38
|
+
...this.#git,
|
|
39
|
+
namespace: artifactsOf(this.#env),
|
|
40
|
+
repositories: this.artifactsRepositories,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Operator-only: one smart-HTTP exchange with an App's template
|
|
44
|
+
* repository, the `git push` that seeds it. */
|
|
45
|
+
async exchangeTemplateSource(rawAppId, suffix, request) {
|
|
46
|
+
return exchangeOysterTemplateSource(this.#artifacts(), rawAppId, suffix, request);
|
|
47
|
+
}
|
|
48
|
+
/** Operator-only: record the seed the operator just pushed to this
|
|
49
|
+
* Oyster's editable App source template, register the platform App itself
|
|
50
|
+
* as this tenant's own publisher so a later `forkApp` can authorize
|
|
51
|
+
* against it (`fork.ts` reads this record), and make that template the
|
|
52
|
+
* App's build source with the recipe its seed receipt pins, so the
|
|
53
|
+
* published seed revision builds on the Computer. */
|
|
54
|
+
async publishSource(rawAppId, claim, teamId) {
|
|
55
|
+
const result = await recordTemplateSeed(this.appSourceReceipts, this.#artifacts(), rawAppId, claim);
|
|
56
|
+
if (result.kind === "recorded" || result.kind === "existing") {
|
|
57
|
+
const appId = parseAppId(rawAppId);
|
|
58
|
+
const registered = await this.appRegistrations.register({
|
|
59
|
+
appId,
|
|
60
|
+
teamId,
|
|
61
|
+
source: {
|
|
62
|
+
provider: "artifacts",
|
|
63
|
+
repository: templateRepositoryForApp(appId),
|
|
64
|
+
branch: "main",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
if (registered.kind === "conflict")
|
|
68
|
+
return { kind: "conflict" };
|
|
69
|
+
const receipt = await this.appSourceReceipts.read(appId, result.receipt.seedRevision);
|
|
70
|
+
if (receipt === null)
|
|
71
|
+
return { kind: "conflict" };
|
|
72
|
+
(await this.#appBuilds()).registerSource({
|
|
73
|
+
appId,
|
|
74
|
+
location: { kind: "template" },
|
|
75
|
+
recipe: receipt.recipe,
|
|
76
|
+
buildRecipeDigest: receipt.buildRecipeDigest,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
/** Fork a published platform App's source into its own Team-owned App,
|
|
82
|
+
* as repository `name` of the calling installation's Git (so its owner
|
|
83
|
+
* edits and merges it there), and register that repository as the new
|
|
84
|
+
* App's build source with the template's pinned recipe, so a merged
|
|
85
|
+
* commit builds without further setup. */
|
|
86
|
+
async forkApp(input) {
|
|
87
|
+
const appId = Schema.decodeUnknownSync(appIdSchema)(standaloneAppForkIds.app.generate());
|
|
88
|
+
const platformAppId = parseAppId(input.platformAppId);
|
|
89
|
+
const forked = await forkAppSource({
|
|
90
|
+
teamId: input.teamId,
|
|
91
|
+
platformAppId,
|
|
92
|
+
appId,
|
|
93
|
+
repository: `${input.repositoryPrefix}${input.name}`,
|
|
94
|
+
}, {
|
|
95
|
+
registry: this.appRegistrations,
|
|
96
|
+
artifacts: this.#artifacts(),
|
|
97
|
+
receipts: this.appSourceReceipts,
|
|
98
|
+
});
|
|
99
|
+
if (forked.kind !== "forked")
|
|
100
|
+
return forked;
|
|
101
|
+
const registration = await this.appRegistrations.read(appId);
|
|
102
|
+
const seed = registration.kind === "found"
|
|
103
|
+
? registration.registration.templateSeed
|
|
104
|
+
: undefined;
|
|
105
|
+
const receipt = seed === undefined
|
|
106
|
+
? null
|
|
107
|
+
: await this.appSourceReceipts.read(seed.appId, seed.revision);
|
|
108
|
+
if (receipt === null)
|
|
109
|
+
return { kind: "conflict" };
|
|
110
|
+
(await this.#appBuilds()).registerSource({
|
|
111
|
+
appId,
|
|
112
|
+
location: {
|
|
113
|
+
kind: "installation",
|
|
114
|
+
installationId: input.installationId,
|
|
115
|
+
repository: input.name,
|
|
116
|
+
},
|
|
117
|
+
recipe: receipt.recipe,
|
|
118
|
+
buildRecipeDigest: receipt.buildRecipeDigest,
|
|
119
|
+
});
|
|
120
|
+
return { kind: "forked", appId, repository: input.name };
|
|
121
|
+
}
|
|
122
|
+
/** The tenant's App build owner on its one fixed Computer. */
|
|
123
|
+
#appBuilds() {
|
|
124
|
+
this.#builds ??= standaloneTenancy(this.#env).then((shared) => {
|
|
125
|
+
const env = this.#env;
|
|
126
|
+
const name = env.OYSTER_COMPUTER_ID;
|
|
127
|
+
return createOysterAppBuilds({
|
|
128
|
+
owner: {
|
|
129
|
+
sql: this.ctx.storage.sql,
|
|
130
|
+
transactionSync: (operation) => this.ctx.storage.transactionSync(operation),
|
|
131
|
+
},
|
|
132
|
+
tenantId: shared.tenantId,
|
|
133
|
+
computer: {
|
|
134
|
+
name,
|
|
135
|
+
objectId: env.COMPUTER.idFromName(name).toString(),
|
|
136
|
+
port: {
|
|
137
|
+
runBuild: (input) => env.COMPUTER.getByName(name).runBuild(input),
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
artifacts: r2ArtifactStore(env.RELEASES),
|
|
141
|
+
templates: this.appSourceReceipts,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
return this.#builds;
|
|
145
|
+
}
|
|
146
|
+
/** Owner: record an App's build source (one repository, pinned recipe). */
|
|
147
|
+
async registerBuildSource(source) {
|
|
148
|
+
(await this.#appBuilds()).registerSource(source);
|
|
149
|
+
}
|
|
150
|
+
/** Owner: start one build of an App's source at one commit; it runs on. */
|
|
151
|
+
async startBuild(input) {
|
|
152
|
+
const { build, run } = await (await this.#appBuilds()).start(input);
|
|
153
|
+
this.ctx.waitUntil(run());
|
|
154
|
+
return build;
|
|
155
|
+
}
|
|
156
|
+
async readBuild(jobId) {
|
|
157
|
+
return (await this.#appBuilds()).read(jobId);
|
|
158
|
+
}
|
|
159
|
+
/** The Computer re-admits each build execution here. */
|
|
160
|
+
async admitBuildExecution(jobId, computerObjectId) {
|
|
161
|
+
return (await this.#appBuilds()).admitExecution(jobId, computerObjectId);
|
|
162
|
+
}
|
|
163
|
+
/** The egress: which live job a build report from this Computer speaks for. */
|
|
164
|
+
async admitBuildReport(jobId, computerObjectId) {
|
|
165
|
+
return (await this.#appBuilds()).admitBuild(jobId, computerObjectId);
|
|
166
|
+
}
|
|
167
|
+
/** The egress: the clone pass for this Computer's live job on one repository. */
|
|
168
|
+
async buildGitPassFor(computerObjectId, target) {
|
|
169
|
+
return (await this.#appBuilds()).gitPassFor(computerObjectId, target);
|
|
170
|
+
}
|
|
171
|
+
/** The Git door: admit a presented build clone pass. */
|
|
172
|
+
async admitBuildGitPass(pass, target) {
|
|
173
|
+
return (await this.#appBuilds()).admitGitPass(pass, target);
|
|
174
|
+
}
|
|
175
|
+
/** The egress: one read-only Git exchange with a template repository, for
|
|
176
|
+
* this Computer's live template build job only. */
|
|
177
|
+
async exchangeBuildTemplate(computerObjectId, repository, request) {
|
|
178
|
+
if (!(await this.#appBuilds()).admitTemplateClone(computerObjectId, repository))
|
|
179
|
+
return new Response(null, { status: 403 });
|
|
180
|
+
return exchangeOysterTemplateGit(this.#artifacts(), `https://git.team.internal/${repository}.git`, repository, request, "read");
|
|
181
|
+
}
|
|
182
|
+
async recordBuildEvent(event) {
|
|
183
|
+
(await this.#appBuilds()).record(event);
|
|
184
|
+
}
|
|
185
|
+
/** The egress: may this Computer's live job upload one object, how large? */
|
|
186
|
+
async admitBuildObject(jobId, computerObjectId, grant) {
|
|
187
|
+
return (await this.#appBuilds()).admitObjectUpload(jobId, computerObjectId, grant);
|
|
188
|
+
}
|
|
189
|
+
/** The egress: a live job's candidate manifest; its objects are stored. */
|
|
190
|
+
async submitBuildCandidate(jobId, manifest, grant, credentialId) {
|
|
191
|
+
return (await this.#appBuilds()).submitCandidate(jobId, manifest, grant, credentialId);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TenancyRegistryDO } from "../tenancy/cloudflare.js";
|
|
2
|
+
import { type Identity, type Tenant, type ReadonlyTenancy } from "../tenancy/index.js";
|
|
3
|
+
import type { OysterGitWorkerEnv } from "../workers-git.js";
|
|
4
|
+
import { type OysterTenancy, type TenantId } from "../tenant.js";
|
|
5
|
+
/** Trusted standalone provisioning inputs; credentials never enter the seed. */
|
|
6
|
+
export type StandaloneSeed = Readonly<{
|
|
7
|
+
origin: string;
|
|
8
|
+
owner: Identity;
|
|
9
|
+
}>;
|
|
10
|
+
/** A fresh registry namespace with one host-provisioned standalone tenant. */
|
|
11
|
+
export declare class StandaloneTenants extends TenancyRegistryDO {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(state: DurableObjectState);
|
|
14
|
+
/** Reuse only the exact configured tenant and its current canonical owner. */
|
|
15
|
+
seed(input: StandaloneSeed): Promise<Tenant>;
|
|
16
|
+
}
|
|
17
|
+
/** The same trusted bindings are used by HTTP, MCP and cold root readers. */
|
|
18
|
+
export type StandaloneTenancyEnv = Pick<OysterGitWorkerEnv, "OYSTER_ORIGIN" | "OYSTER_OWNER_NAME" | "OYSTER_OWNER_KEY" | "KEYS"> & Readonly<{
|
|
19
|
+
TENANTS: DurableObjectNamespace<StandaloneTenants>;
|
|
20
|
+
}>;
|
|
21
|
+
/** A live registry-owned owner; the configured label is never its member ID. */
|
|
22
|
+
export type StandaloneOwner = Readonly<{
|
|
23
|
+
actor: Readonly<{
|
|
24
|
+
kind: "member";
|
|
25
|
+
memberId: string;
|
|
26
|
+
}>;
|
|
27
|
+
role: "owner";
|
|
28
|
+
membershipVersion: number;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Read-only kit authority and the canonical Oyster bridge share one seed.
|
|
32
|
+
* Every entry admits with exactly one TENANTS read: an HTTP request through
|
|
33
|
+
* `oyster.resolve`, and each `owner()` or `tenancy` call at its own top.
|
|
34
|
+
*/
|
|
35
|
+
export type StandaloneTenancy = Readonly<{
|
|
36
|
+
tenantId: TenantId;
|
|
37
|
+
tenancy: ReadonlyTenancy<"origin">;
|
|
38
|
+
oyster: OysterTenancy;
|
|
39
|
+
owner(): Promise<StandaloneOwner | null>;
|
|
40
|
+
}>;
|
|
41
|
+
/** Seed once per environment, then admit every entry from one fresh registry read. */
|
|
42
|
+
export declare function standaloneTenancy(env: StandaloneTenancyEnv): Promise<StandaloneTenancy>;
|