@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
package/dist/r2-files.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Files in an R2 bucket under `prefix` (one workspace per prefix). */
|
|
2
|
+
export function r2FileSource(bucket, prefix) {
|
|
3
|
+
const key = (path) => `${prefix}${path.slice(1)}`;
|
|
4
|
+
const dirKey = (path) => (path === "/" ? prefix : `${key(path)}/`);
|
|
5
|
+
return {
|
|
6
|
+
async list(path, limit, offset) {
|
|
7
|
+
const base = dirKey(path);
|
|
8
|
+
const entries = [];
|
|
9
|
+
let cursor;
|
|
10
|
+
do {
|
|
11
|
+
const options = { prefix: base, delimiter: "/" };
|
|
12
|
+
if (cursor !== undefined)
|
|
13
|
+
options.cursor = cursor;
|
|
14
|
+
const page = await bucket.list(options);
|
|
15
|
+
for (const folder of page.delimitedPrefixes)
|
|
16
|
+
entries.push({
|
|
17
|
+
name: folder.slice(base.length, -1),
|
|
18
|
+
type: "directory",
|
|
19
|
+
size: 0,
|
|
20
|
+
modifiedAt: 0,
|
|
21
|
+
});
|
|
22
|
+
for (const object of page.objects) {
|
|
23
|
+
const name = object.key.slice(base.length);
|
|
24
|
+
// A directory marker is the directory itself, not an entry.
|
|
25
|
+
if (name === "")
|
|
26
|
+
continue;
|
|
27
|
+
entries.push({
|
|
28
|
+
name,
|
|
29
|
+
type: "file",
|
|
30
|
+
size: object.size,
|
|
31
|
+
modifiedAt: object.uploaded.getTime(),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
cursor = page.truncated ? page.cursor : undefined;
|
|
35
|
+
} while (cursor !== undefined && entries.length < offset + limit + 1);
|
|
36
|
+
// An empty, unmarked directory other than the root does not exist.
|
|
37
|
+
if (entries.length === 0 &&
|
|
38
|
+
path !== "/" &&
|
|
39
|
+
(await bucket.head(dirKey(path))) === null)
|
|
40
|
+
return null;
|
|
41
|
+
entries.sort((a, b) => a.type === b.type
|
|
42
|
+
? a.name.localeCompare(b.name)
|
|
43
|
+
: a.type === "directory"
|
|
44
|
+
? -1
|
|
45
|
+
: 1);
|
|
46
|
+
const page = entries.slice(offset, offset + limit);
|
|
47
|
+
return {
|
|
48
|
+
path,
|
|
49
|
+
entries: page,
|
|
50
|
+
nextOffset: offset + limit < entries.length ? offset + limit : null,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
async read(path) {
|
|
54
|
+
const object = await bucket.get(key(path));
|
|
55
|
+
return object === null
|
|
56
|
+
? null
|
|
57
|
+
: new Uint8Array(await object.arrayBuffer());
|
|
58
|
+
},
|
|
59
|
+
async write(path, content) {
|
|
60
|
+
await bucket.put(key(path), content);
|
|
61
|
+
},
|
|
62
|
+
async mkdir(path) {
|
|
63
|
+
await bucket.put(dirKey(path), new Uint8Array());
|
|
64
|
+
},
|
|
65
|
+
async replace(path, expectedContent, content) {
|
|
66
|
+
const current = await bucket.get(key(path));
|
|
67
|
+
if (current === null || (await current.text()) !== expectedContent)
|
|
68
|
+
return { kind: "conflict" };
|
|
69
|
+
const bytes = new TextEncoder().encode(content);
|
|
70
|
+
// Compare-and-swap: R2 refuses the write if the object changed since the read.
|
|
71
|
+
const written = await bucket.put(key(path), bytes, {
|
|
72
|
+
onlyIf: { etagMatches: current.etag },
|
|
73
|
+
});
|
|
74
|
+
return written === null
|
|
75
|
+
? { kind: "conflict" }
|
|
76
|
+
: { kind: "replaced", path, bytes: bytes.length };
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type AppApi, type AppBuild, type AppDeclaration } from "@fungi.computer/puffball/app";
|
|
2
|
+
import { type AssetBuild, type AssetBuildPolicy } from "@fungi.computer/puffball/assets";
|
|
3
|
+
import { type DurableBackend } from "@fungi.computer/puffball/backend";
|
|
4
|
+
import type { ArtifactRef, Release, Sha256Digest } from "@fungi.computer/puffball";
|
|
5
|
+
/** Immutable Release bytes addressed by their artifact reference. */
|
|
6
|
+
export type ArtifactStore = Readonly<{
|
|
7
|
+
/** Read the stored bytes for `artifactRef`, or `null` when absent. */
|
|
8
|
+
get: (artifactRef: ArtifactRef) => Promise<Uint8Array | null>;
|
|
9
|
+
/** Store bytes under `artifactRef`; the caller has verified the digest. */
|
|
10
|
+
put: (artifactRef: ArtifactRef, bytes: Uint8Array) => Promise<void>;
|
|
11
|
+
}>;
|
|
12
|
+
/** What a store knows of one object without reading it. */
|
|
13
|
+
export type ArtifactHead = Readonly<{
|
|
14
|
+
byteLength: number;
|
|
15
|
+
/** The digest the store verified when it took the bytes, if it did. */
|
|
16
|
+
digest: Sha256Digest | null;
|
|
17
|
+
}>;
|
|
18
|
+
/** A store that can describe an object without reading its bytes. */
|
|
19
|
+
export type ArtifactHeads = Readonly<{
|
|
20
|
+
/** Describe the object at `artifactRef`, or `null` when absent. */
|
|
21
|
+
head: (artifactRef: ArtifactRef) => Promise<ArtifactHead | null>;
|
|
22
|
+
}>;
|
|
23
|
+
/** Why a Release could not be read; the host maps it to an HTTP refusal. */
|
|
24
|
+
export declare class ReleaseError extends Error {
|
|
25
|
+
readonly code: "artifact_missing" | "digest_mismatch" | "invalid_release" | "asset_missing";
|
|
26
|
+
readonly name = "ReleaseError";
|
|
27
|
+
constructor(code: "artifact_missing" | "digest_mismatch" | "invalid_release" | "asset_missing", message: string, options?: ErrorOptions);
|
|
28
|
+
}
|
|
29
|
+
/** Default bounds for an App's UI asset build. */
|
|
30
|
+
export declare const defaultAssetPolicy: AssetBuildPolicy;
|
|
31
|
+
/** A Release whose root, UI and backend artifacts were read and verified. */
|
|
32
|
+
export type VerifiedRelease = Readonly<{
|
|
33
|
+
release: Release;
|
|
34
|
+
build: AppBuild;
|
|
35
|
+
/** The App API declared by the Release, or `null` when it has none. */
|
|
36
|
+
api: AppApi | null;
|
|
37
|
+
declaration: AppDeclaration | null;
|
|
38
|
+
ui: Readonly<{
|
|
39
|
+
kind: "html";
|
|
40
|
+
artifactRef: ArtifactRef;
|
|
41
|
+
digest: Sha256Digest;
|
|
42
|
+
}> | Readonly<{
|
|
43
|
+
kind: "assets";
|
|
44
|
+
build: AssetBuild;
|
|
45
|
+
}> | null;
|
|
46
|
+
backend: Readonly<{
|
|
47
|
+
artifactRef: ArtifactRef;
|
|
48
|
+
digest: Sha256Digest;
|
|
49
|
+
}> | null;
|
|
50
|
+
}>;
|
|
51
|
+
/** Hex SHA-256 digest in Puffball's `sha256:<hex>` form. */
|
|
52
|
+
export declare function sha256(bytes: Uint8Array): Promise<string>;
|
|
53
|
+
/** Read one artifact and refuse it unless its bytes match `digest`. */
|
|
54
|
+
export declare function readVerified(store: ArtifactStore, artifactRef: ArtifactRef, digest: Sha256Digest): Promise<Uint8Array>;
|
|
55
|
+
/** Decode an untrusted Release record, as an install request carries it. */
|
|
56
|
+
export declare function parseRelease(value: unknown): Release;
|
|
57
|
+
/**
|
|
58
|
+
* Read a Release root and verify every artifact it names: the UI document or
|
|
59
|
+
* asset build (and each asset object), and the backend descriptor.
|
|
60
|
+
*/
|
|
61
|
+
export declare function verifyRelease(store: ArtifactStore, release: Release, policy?: AssetBuildPolicy,
|
|
62
|
+
/**
|
|
63
|
+
* Read and check every UI asset now. Serving can skip this: each asset is
|
|
64
|
+
* verified against its digest when it is read.
|
|
65
|
+
*/
|
|
66
|
+
readAssets?: boolean): Promise<VerifiedRelease>;
|
|
67
|
+
/**
|
|
68
|
+
* Resolve a Release to its verified durable backend without reading its UI:
|
|
69
|
+
* the root artifact, then the backend descriptor it names.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveBackend(store: ArtifactStore, release: Release): Promise<Readonly<{
|
|
72
|
+
digest: Sha256Digest;
|
|
73
|
+
backend: DurableBackend;
|
|
74
|
+
declaration: AppDeclaration | null;
|
|
75
|
+
}> | null>;
|
|
76
|
+
/** A verified backend, as {@link resolveBackend} returns it. */
|
|
77
|
+
export type ResolvedBackend = NonNullable<Awaited<ReturnType<typeof resolveBackend>>>;
|
|
78
|
+
/**
|
|
79
|
+
* Verify each Release's backend once per owner. A Release's artifacts are
|
|
80
|
+
* immutable and content-addressed, so a verified read is reused for the same
|
|
81
|
+
* Release ID and artifact digest; a failed read is not cached.
|
|
82
|
+
*/
|
|
83
|
+
export declare function backendCache(store: ArtifactStore): (release: Release) => Promise<ResolvedBackend | null>;
|
package/dist/release.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import { appBuildJsonSchema, } from "@fungi.computer/puffball/app";
|
|
3
|
+
import { assetBuildMediaType, decodeAssetBuild, } from "@fungi.computer/puffball/assets";
|
|
4
|
+
import { backendJsonSchema, } from "@fungi.computer/puffball/backend";
|
|
5
|
+
import { releaseSchema } from "@fungi.computer/puffball/effect";
|
|
6
|
+
/** Why a Release could not be read; the host maps it to an HTTP refusal. */
|
|
7
|
+
export class ReleaseError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
name = "ReleaseError";
|
|
10
|
+
constructor(code, message, options) {
|
|
11
|
+
super(message, options);
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Default bounds for an App's UI asset build. */
|
|
16
|
+
export const defaultAssetPolicy = {
|
|
17
|
+
maxManifestBytes: 1024 * 1024,
|
|
18
|
+
maxObjects: 2_000,
|
|
19
|
+
maxObjectBytes: 25 * 1024 * 1024,
|
|
20
|
+
maxTotalBytes: 100 * 1024 * 1024,
|
|
21
|
+
};
|
|
22
|
+
/** Hex SHA-256 digest in Puffball's `sha256:<hex>` form. */
|
|
23
|
+
export async function sha256(bytes) {
|
|
24
|
+
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", new Uint8Array(bytes)));
|
|
25
|
+
let hex = "";
|
|
26
|
+
for (const byte of hash)
|
|
27
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
28
|
+
return `sha256:${hex}`;
|
|
29
|
+
}
|
|
30
|
+
/** Read one artifact and refuse it unless its bytes match `digest`. */
|
|
31
|
+
export async function readVerified(store, artifactRef, digest) {
|
|
32
|
+
const bytes = await store.get(artifactRef);
|
|
33
|
+
if (bytes === null)
|
|
34
|
+
throw new ReleaseError("artifact_missing", `Missing ${artifactRef}`);
|
|
35
|
+
if ((await sha256(bytes)) !== digest)
|
|
36
|
+
throw new ReleaseError("digest_mismatch", `Digest mismatch ${artifactRef}`);
|
|
37
|
+
return bytes;
|
|
38
|
+
}
|
|
39
|
+
const decodeText = (bytes) => new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(bytes);
|
|
40
|
+
/** Decode an untrusted Release record, as an install request carries it. */
|
|
41
|
+
export function parseRelease(value) {
|
|
42
|
+
try {
|
|
43
|
+
return Schema.decodeUnknownSync(releaseSchema)(value, {
|
|
44
|
+
onExcessProperty: "error",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (cause) {
|
|
48
|
+
throw new ReleaseError("invalid_release", "Invalid Release", { cause });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read a Release root and verify every artifact it names: the UI document or
|
|
53
|
+
* asset build (and each asset object), and the backend descriptor.
|
|
54
|
+
*/
|
|
55
|
+
export async function verifyRelease(store, release, policy = defaultAssetPolicy,
|
|
56
|
+
/**
|
|
57
|
+
* Read and check every UI asset now. Serving can skip this: each asset is
|
|
58
|
+
* verified against its digest when it is read.
|
|
59
|
+
*/
|
|
60
|
+
readAssets = true) {
|
|
61
|
+
const rootBytes = await readVerified(store, release.artifact.artifactRef, release.artifact.artifactDigest);
|
|
62
|
+
let build;
|
|
63
|
+
try {
|
|
64
|
+
build = Schema.decodeUnknownSync(appBuildJsonSchema)(decodeText(rootBytes), {
|
|
65
|
+
onExcessProperty: "error",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (cause) {
|
|
69
|
+
throw new ReleaseError("invalid_release", "Invalid App root", { cause });
|
|
70
|
+
}
|
|
71
|
+
let ui = null;
|
|
72
|
+
if ("ui" in build) {
|
|
73
|
+
const bytes = await readVerified(store, build.ui.artifactRef, build.ui.digest);
|
|
74
|
+
if (build.ui.mediaType === assetBuildMediaType) {
|
|
75
|
+
let assets;
|
|
76
|
+
try {
|
|
77
|
+
assets = decodeAssetBuild(bytes, policy);
|
|
78
|
+
}
|
|
79
|
+
catch (cause) {
|
|
80
|
+
throw new ReleaseError("invalid_release", "Invalid assets", { cause });
|
|
81
|
+
}
|
|
82
|
+
// One asset at a time: a Release's assets together can exceed an
|
|
83
|
+
// isolate's memory, each alone is bounded by its policy.
|
|
84
|
+
if (readAssets)
|
|
85
|
+
for (const asset of assets.assets)
|
|
86
|
+
await readVerified(store, asset.artifactRef, asset.digest);
|
|
87
|
+
ui = { kind: "assets", build: assets };
|
|
88
|
+
}
|
|
89
|
+
else
|
|
90
|
+
ui = {
|
|
91
|
+
kind: "html",
|
|
92
|
+
artifactRef: build.ui.artifactRef,
|
|
93
|
+
digest: build.ui.digest,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
let backend = null;
|
|
97
|
+
if ("backend" in build) {
|
|
98
|
+
await readBackend(store, build.backend.artifactRef, build.backend.digest);
|
|
99
|
+
backend = {
|
|
100
|
+
artifactRef: build.backend.artifactRef,
|
|
101
|
+
digest: build.backend.digest,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
release,
|
|
106
|
+
build,
|
|
107
|
+
api: build.declaration?.api ?? null,
|
|
108
|
+
declaration: build.declaration ?? null,
|
|
109
|
+
ui,
|
|
110
|
+
backend,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** Read and decode a verified durable backend descriptor. */
|
|
114
|
+
async function readBackend(store, artifactRef, digest) {
|
|
115
|
+
const bytes = await readVerified(store, artifactRef, digest);
|
|
116
|
+
try {
|
|
117
|
+
return Schema.decodeUnknownSync(backendJsonSchema)(decodeText(bytes), {
|
|
118
|
+
onExcessProperty: "error",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (cause) {
|
|
122
|
+
throw new ReleaseError("invalid_release", "Invalid backend", { cause });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a Release to its verified durable backend without reading its UI:
|
|
127
|
+
* the root artifact, then the backend descriptor it names.
|
|
128
|
+
*/
|
|
129
|
+
export async function resolveBackend(store, release) {
|
|
130
|
+
const rootBytes = await readVerified(store, release.artifact.artifactRef, release.artifact.artifactDigest);
|
|
131
|
+
let build;
|
|
132
|
+
try {
|
|
133
|
+
build = Schema.decodeUnknownSync(appBuildJsonSchema)(decodeText(rootBytes), {
|
|
134
|
+
onExcessProperty: "error",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (cause) {
|
|
138
|
+
throw new ReleaseError("invalid_release", "Invalid App root", { cause });
|
|
139
|
+
}
|
|
140
|
+
if (!("backend" in build))
|
|
141
|
+
return null;
|
|
142
|
+
return {
|
|
143
|
+
digest: build.backend.digest,
|
|
144
|
+
declaration: build.declaration ?? null,
|
|
145
|
+
backend: await readBackend(store, build.backend.artifactRef, build.backend.digest),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Verify each Release's backend once per owner. A Release's artifacts are
|
|
150
|
+
* immutable and content-addressed, so a verified read is reused for the same
|
|
151
|
+
* Release ID and artifact digest; a failed read is not cached.
|
|
152
|
+
*/
|
|
153
|
+
export function backendCache(store) {
|
|
154
|
+
const verified = new Map();
|
|
155
|
+
return (release) => {
|
|
156
|
+
const key = `${release.releaseId}:${release.artifact.artifactDigest}`;
|
|
157
|
+
let entry = verified.get(key);
|
|
158
|
+
if (entry === undefined) {
|
|
159
|
+
entry = resolveBackend(store, release).catch((cause) => {
|
|
160
|
+
verified.delete(key);
|
|
161
|
+
throw cause;
|
|
162
|
+
});
|
|
163
|
+
verified.set(key, entry);
|
|
164
|
+
}
|
|
165
|
+
return entry;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Release } from "@fungi.computer/puffball";
|
|
2
|
+
import { createAppStorage } from "./app-storage.js";
|
|
3
|
+
import type { InstallationStore, verifyRelease } from "./host.js";
|
|
4
|
+
import { type AppDirectoryEntry } from "./shell/app-directory.js";
|
|
5
|
+
import type { WorkbenchPlatformDestination } from "./shell/workbench-model.js";
|
|
6
|
+
/** Role choices belong to the root owner, independently of Release declarations. */
|
|
7
|
+
export type OysterRootRoles = Readonly<Record<string, Readonly<{
|
|
8
|
+
surfaceId: string | null;
|
|
9
|
+
chosen: boolean;
|
|
10
|
+
}>>>;
|
|
11
|
+
/** Shared workbench metadata, without a hosted Team's administration or grants. */
|
|
12
|
+
export type OysterRootDirectory = Readonly<{
|
|
13
|
+
apps: readonly AppDirectoryEntry[];
|
|
14
|
+
roles: OysterRootRoles;
|
|
15
|
+
/** Fixed host-selected Computer context; metadata conveys no operation rights. */
|
|
16
|
+
computer?: Readonly<{
|
|
17
|
+
computerId: string;
|
|
18
|
+
name: string;
|
|
19
|
+
}>;
|
|
20
|
+
}>;
|
|
21
|
+
/** Existing installation, Release and SQLite owners supplied by the root. */
|
|
22
|
+
export type OysterRootDataOptions = Parameters<typeof createAppStorage>[0] & Readonly<{
|
|
23
|
+
installations: Pick<InstallationStore, "list">;
|
|
24
|
+
/** Reuse the root's verified Release resolver; metadata grants no access. */
|
|
25
|
+
resolveRelease(release: Release): Promise<Pick<Awaited<ReturnType<typeof verifyRelease>>, "declaration" | "ui">>;
|
|
26
|
+
}>;
|
|
27
|
+
/** Data adapters for a standalone workbench over the root's existing owners. */
|
|
28
|
+
export declare function createOysterRootData(options: OysterRootDataOptions): {
|
|
29
|
+
/** Each open(authorize) receives fresh host admission and Release-declared keys. */
|
|
30
|
+
storage: {
|
|
31
|
+
purgeInstallation: (address: string) => void;
|
|
32
|
+
purgeMember: (memberId: string, throughVersion: number) => void;
|
|
33
|
+
openInstallation(authorize: () => import("effect/Effect").Effect<import("./app-storage.js").AppStorageInstallationAdmission, Error>): {
|
|
34
|
+
read: (key: string) => import("effect/Effect").Effect<{
|
|
35
|
+
readonly value: import("effect/Schema").Json;
|
|
36
|
+
readonly source: "team" | "member" | "none";
|
|
37
|
+
readonly team: {
|
|
38
|
+
readonly value: import("effect/Schema").Json;
|
|
39
|
+
readonly revision: number;
|
|
40
|
+
} | null;
|
|
41
|
+
readonly member: {
|
|
42
|
+
readonly value: import("effect/Schema").Json;
|
|
43
|
+
readonly revision: number;
|
|
44
|
+
} | null;
|
|
45
|
+
readonly canWriteTeam: boolean;
|
|
46
|
+
}, import("@fungi.computer/agar").AppStorageError, never>;
|
|
47
|
+
write: (input: import("@fungi.computer/agar").AppStorageWrite) => import("effect/Effect").Effect<{
|
|
48
|
+
revision: number;
|
|
49
|
+
}, import("@fungi.computer/agar").AppStorageError, never>;
|
|
50
|
+
reset: (input: import("@fungi.computer/agar").AppStorageReset) => import("effect/Effect").Effect<{
|
|
51
|
+
revision: number;
|
|
52
|
+
}, import("@fungi.computer/agar").AppStorageError, never>;
|
|
53
|
+
};
|
|
54
|
+
open(authorize: () => import("effect/Effect").Effect<import("./app-storage.js").AppStorageAdmission, Error>): {
|
|
55
|
+
read: (key: string) => import("effect/Effect").Effect<{
|
|
56
|
+
readonly value: import("effect/Schema").Json;
|
|
57
|
+
readonly source: "team" | "member" | "none";
|
|
58
|
+
readonly team: {
|
|
59
|
+
readonly value: import("effect/Schema").Json;
|
|
60
|
+
readonly revision: number;
|
|
61
|
+
} | null;
|
|
62
|
+
readonly member: {
|
|
63
|
+
readonly value: import("effect/Schema").Json;
|
|
64
|
+
readonly revision: number;
|
|
65
|
+
} | null;
|
|
66
|
+
readonly canWriteTeam: boolean;
|
|
67
|
+
}, import("@fungi.computer/agar").AppStorageError, never>;
|
|
68
|
+
write: (input: import("@fungi.computer/agar").AppStorageWrite) => import("effect/Effect").Effect<{
|
|
69
|
+
revision: number;
|
|
70
|
+
}, import("@fungi.computer/agar").AppStorageError, never>;
|
|
71
|
+
reset: (input: import("@fungi.computer/agar").AppStorageReset) => import("effect/Effect").Effect<{
|
|
72
|
+
revision: number;
|
|
73
|
+
}, import("@fungi.computer/agar").AppStorageError, never>;
|
|
74
|
+
events: (key: string) => import("effect/Stream").Stream<{
|
|
75
|
+
readonly appId: string;
|
|
76
|
+
readonly key: string;
|
|
77
|
+
readonly scope: "team" | "member";
|
|
78
|
+
readonly revision: number;
|
|
79
|
+
} | null, import("@fungi.computer/agar").AppStorageError, never>;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
/** Project the current installations without minting permissions or role choices. */
|
|
83
|
+
directory(): Promise<OysterRootDirectory>;
|
|
84
|
+
/** No standalone platform navigation destination is implemented yet. */
|
|
85
|
+
platformDestinations(): readonly WorkbenchPlatformDestination[];
|
|
86
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { resolveAppRoles, } from "@fungi.computer/puffball/app";
|
|
2
|
+
import { createAppStorage } from "./app-storage.js";
|
|
3
|
+
import { addressKey, appDirectoryEntrySchema, } from "./shell/app-directory.js";
|
|
4
|
+
/** Data adapters for a standalone workbench over the root's existing owners. */
|
|
5
|
+
export function createOysterRootData(options) {
|
|
6
|
+
const storage = createAppStorage(options);
|
|
7
|
+
return {
|
|
8
|
+
/** Each open(authorize) receives fresh host admission and Release-declared keys. */
|
|
9
|
+
storage,
|
|
10
|
+
/** Project the current installations without minting permissions or role choices. */
|
|
11
|
+
async directory() {
|
|
12
|
+
const installations = await options.installations.list();
|
|
13
|
+
const projected = await Promise.all(installations
|
|
14
|
+
.filter((installation) => installation.status !== "deleted")
|
|
15
|
+
.map(async (installation) => {
|
|
16
|
+
const resolved = await options.resolveRelease(installation.release);
|
|
17
|
+
const declaration = resolved.declaration;
|
|
18
|
+
const address = {
|
|
19
|
+
kind: "installation",
|
|
20
|
+
installationId: installation.installationId,
|
|
21
|
+
};
|
|
22
|
+
const surfaceId = addressKey(address);
|
|
23
|
+
const surfacePath = `/apps/${encodeURIComponent(installation.installationId)}/`;
|
|
24
|
+
const available = installation.status === "enabled" && resolved.ui !== null;
|
|
25
|
+
const availability = available ? "available" : "unavailable";
|
|
26
|
+
const entry = {
|
|
27
|
+
address,
|
|
28
|
+
installationId: installation.installationId,
|
|
29
|
+
surfaceId,
|
|
30
|
+
appId: installation.release.appId,
|
|
31
|
+
displayName: declaration?.displayName,
|
|
32
|
+
icon: declaration?.icon ?? null,
|
|
33
|
+
releaseId: installation.release.releaseId,
|
|
34
|
+
authorityVersion: installation.authorityVersion,
|
|
35
|
+
enforcement: null,
|
|
36
|
+
handlers: [...(declaration?.handlers ?? [])],
|
|
37
|
+
roles: [...(declaration?.roles ?? [])],
|
|
38
|
+
declaredCapabilities: [...(declaration?.capabilities ?? [])],
|
|
39
|
+
commands: [...(declaration?.commands ?? [])],
|
|
40
|
+
gills: [...(declaration?.gills ?? [])],
|
|
41
|
+
availability,
|
|
42
|
+
surface: available ? { stableRoute: surfacePath } : null,
|
|
43
|
+
surfacePath,
|
|
44
|
+
};
|
|
45
|
+
if (declaration?.calls !== undefined)
|
|
46
|
+
entry.calls = declaration.calls;
|
|
47
|
+
// Built from the declaration's own typed roles, not the
|
|
48
|
+
// directory entry's widened (string[]) projection of them.
|
|
49
|
+
const candidate = {
|
|
50
|
+
surfaceId,
|
|
51
|
+
availability,
|
|
52
|
+
roles: declaration?.roles ?? [],
|
|
53
|
+
};
|
|
54
|
+
return { entry: appDirectoryEntrySchema.parse(entry), candidate };
|
|
55
|
+
}));
|
|
56
|
+
// Standalone has no PUT route and no stored choice: per the ADR, the
|
|
57
|
+
// effective holder of an unchosen role is the first available listed
|
|
58
|
+
// App declaring it.
|
|
59
|
+
return {
|
|
60
|
+
apps: projected.map((app) => app.entry),
|
|
61
|
+
roles: resolveAppRoles(new Map(), projected.map((app) => app.candidate)),
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
/** No standalone platform navigation destination is implemented yet. */
|
|
65
|
+
platformDestinations() {
|
|
66
|
+
return [];
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|