@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,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A build's uploaded objects in an R2-shaped store: content addressed
|
|
3
|
+
* (`artifact:sha256-<hex>`), written once from a stream with the store
|
|
4
|
+
* itself verifying the digest, and described by `head()` without a read.
|
|
5
|
+
* The bytes never pass through an isolate's memory as a whole.
|
|
6
|
+
*/
|
|
7
|
+
import { type ArtifactRef, type Sha256Digest } from "@fungi.computer/puffball";
|
|
8
|
+
import type { ArtifactHead } from "../release.js";
|
|
9
|
+
import type { CandidateObject, CandidateObjectMediaType } from "./submission-contract.js";
|
|
10
|
+
/** The parts of an R2 bucket a content-object store uses. */
|
|
11
|
+
export type ContentObjectBucket = Readonly<{
|
|
12
|
+
/** Describe the object at `key` without its body, or `null` when absent. */
|
|
13
|
+
head(key: string): Promise<Readonly<{
|
|
14
|
+
size: number;
|
|
15
|
+
checksums: Readonly<{
|
|
16
|
+
sha256?: ArrayBuffer;
|
|
17
|
+
}>;
|
|
18
|
+
}> | null>;
|
|
19
|
+
/** Store `value` at `key`; the bucket refuses bytes whose SHA-256 differs. */
|
|
20
|
+
put(key: string, value: ReadableStream<Uint8Array> | Uint8Array, options: Readonly<{
|
|
21
|
+
sha256: string;
|
|
22
|
+
httpMetadata: Readonly<{
|
|
23
|
+
contentType: CandidateObjectMediaType;
|
|
24
|
+
}>;
|
|
25
|
+
}>): Promise<object | null>;
|
|
26
|
+
}>;
|
|
27
|
+
/** The store key of one content-addressed object. */
|
|
28
|
+
export declare function contentObjectRef(digest: Sha256Digest): ArtifactRef;
|
|
29
|
+
/** Describe a stored object by its size and the SHA-256 the store verified. */
|
|
30
|
+
export declare function headContentObject(bucket: ContentObjectBucket, artifactRef: ArtifactRef): Promise<ArtifactHead | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Stream one object into the store under its digest. The store refuses a
|
|
33
|
+
* body whose SHA-256 differs; an object already stored with the same
|
|
34
|
+
* verified digest and size is kept (write-once: a stored version may be
|
|
35
|
+
* pinned by a Release) and the body is discarded, never stored.
|
|
36
|
+
*/
|
|
37
|
+
export declare function putContentObject(bucket: ContentObjectBucket, object: CandidateObject, body: ReadableStream<Uint8Array> | null, mediaType: CandidateObjectMediaType): Promise<"stored" | "refused">;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A build's uploaded objects in an R2-shaped store: content addressed
|
|
3
|
+
* (`artifact:sha256-<hex>`), written once from a stream with the store
|
|
4
|
+
* itself verifying the digest, and described by `head()` without a read.
|
|
5
|
+
* The bytes never pass through an isolate's memory as a whole.
|
|
6
|
+
*/
|
|
7
|
+
import { parseArtifactRef, parseSha256Digest, } from "@fungi.computer/puffball";
|
|
8
|
+
/** The store key of one content-addressed object. */
|
|
9
|
+
export function contentObjectRef(digest) {
|
|
10
|
+
return parseArtifactRef(`artifact:sha256-${digest.slice("sha256:".length)}`);
|
|
11
|
+
}
|
|
12
|
+
function hex(bytes) {
|
|
13
|
+
return Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
14
|
+
}
|
|
15
|
+
/** Describe a stored object by its size and the SHA-256 the store verified. */
|
|
16
|
+
export async function headContentObject(bucket, artifactRef) {
|
|
17
|
+
const object = await bucket.head(artifactRef);
|
|
18
|
+
if (object === null)
|
|
19
|
+
return null;
|
|
20
|
+
const sha256 = object.checksums.sha256;
|
|
21
|
+
return {
|
|
22
|
+
byteLength: object.size,
|
|
23
|
+
digest: sha256 === undefined ? null : parseSha256Digest(`sha256:${hex(sha256)}`),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Stream one object into the store under its digest. The store refuses a
|
|
28
|
+
* body whose SHA-256 differs; an object already stored with the same
|
|
29
|
+
* verified digest and size is kept (write-once: a stored version may be
|
|
30
|
+
* pinned by a Release) and the body is discarded, never stored.
|
|
31
|
+
*/
|
|
32
|
+
export async function putContentObject(bucket, object, body, mediaType) {
|
|
33
|
+
const artifactRef = contentObjectRef(object.digest);
|
|
34
|
+
const existing = await headContentObject(bucket, artifactRef);
|
|
35
|
+
if (existing !== null &&
|
|
36
|
+
existing.digest === object.digest &&
|
|
37
|
+
existing.byteLength === object.byteLength) {
|
|
38
|
+
// Read the body through (discarding it) rather than cancel it: a
|
|
39
|
+
// cancelled request body resets the uploader's connection mid-send.
|
|
40
|
+
await body?.pipeTo(new WritableStream());
|
|
41
|
+
return "stored";
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await bucket.put(artifactRef, body ?? new Uint8Array(0), {
|
|
45
|
+
sha256: object.digest.slice("sha256:".length),
|
|
46
|
+
httpMetadata: { contentType: mediaType },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return "refused";
|
|
51
|
+
}
|
|
52
|
+
return "stored";
|
|
53
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type * as esbuild from "esbuild";
|
|
2
|
+
import type { AppDeclaration } from "@fungi.computer/puffball";
|
|
3
|
+
import type { CapturedSource, PinnedRecipe } from "./effect.js";
|
|
4
|
+
/** Package entry policy pinned only by the captured source recipes that need it. */
|
|
5
|
+
export declare function appBackendPackageMainFields(recipe: PinnedRecipe): string[] | undefined;
|
|
6
|
+
/** The checked Worker output of one App backend build. */
|
|
7
|
+
export type AppBackendOutput = Readonly<{
|
|
8
|
+
file: esbuild.OutputFile;
|
|
9
|
+
/** Source inputs contributing to the Worker bundle. */
|
|
10
|
+
inputs: readonly string[];
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Build one App backend and derive its declaration from that same build.
|
|
14
|
+
*
|
|
15
|
+
* A declared `fungi.backendDefinition` adds a second, build-only entry that
|
|
16
|
+
* derives the declaration with `buildAppDeclaration(appBackend, fungi.app)`.
|
|
17
|
+
* esbuild loads every path and resolves every import once per build, so the
|
|
18
|
+
* Worker serves exactly the definition bytes and module resolution the
|
|
19
|
+
* declaration describes. The Worker output is then checked to include that
|
|
20
|
+
* definition and no build-only declaration code, and the declaration output to
|
|
21
|
+
* import nothing at runtime.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createDefinitionEmission(source: CapturedSource, recipe: PinnedRecipe): Promise<{
|
|
24
|
+
/** Entries for the one backend build: the Worker and, if defined, its declaration. */
|
|
25
|
+
entryPoints: (entry: string) => Record<string, string>;
|
|
26
|
+
outdir: string;
|
|
27
|
+
plugins: esbuild.Plugin[];
|
|
28
|
+
/** Check one finished backend build and return its Worker output. */
|
|
29
|
+
worker(result: esbuild.BuildResult): AppBackendOutput;
|
|
30
|
+
/** Evaluate the declaration derived in the checked backend build. */
|
|
31
|
+
declare(signal: AbortSignal): Promise<AppDeclaration>;
|
|
32
|
+
}>;
|
|
33
|
+
/** One App backend build and the declaration derived from it. */
|
|
34
|
+
export type DefinitionEmission = Awaited<ReturnType<typeof createDefinitionEmission>>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { access, readFile, realpath, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
import { appDeclarationSchema } from "@fungi.computer/puffball/app";
|
|
8
|
+
import { emptyAppDeclaration } from "@fungi.computer/puffball";
|
|
9
|
+
const packageSchema = Schema.fromJsonString(Schema.Struct({
|
|
10
|
+
fungi: Schema.optional(Schema.Struct({
|
|
11
|
+
app: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
12
|
+
backendDefinition: Schema.optional(Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256))),
|
|
13
|
+
})),
|
|
14
|
+
}));
|
|
15
|
+
// The Computer VM is the customer-code sandbox. These rules enforce the SDK's
|
|
16
|
+
// runtime-neutral definition convention; they are not a Node security sandbox.
|
|
17
|
+
const evaluateDefinition = `
|
|
18
|
+
import { registerHooks } from 'node:module';
|
|
19
|
+
const entry = process.argv[1];
|
|
20
|
+
const write = process.stdout.write.bind(process.stdout);
|
|
21
|
+
const writeError = process.stderr.write.bind(process.stderr);
|
|
22
|
+
const exit = process.exit.bind(process);
|
|
23
|
+
registerHooks({ resolve(specifier, context, next) {
|
|
24
|
+
if (specifier !== entry) throw new Error('App backend definition imports ' + specifier + '. Move runtime-specific imports into the Worker composition; keep the definition runtime-neutral.');
|
|
25
|
+
return next(specifier, context);
|
|
26
|
+
}});
|
|
27
|
+
globalThis.fetch = () => { throw new Error('App backend definition attempted network access. Move network calls into method handlers; evaluation must be runtime-neutral.'); };
|
|
28
|
+
globalThis.WebSocket = undefined;
|
|
29
|
+
globalThis.process = undefined;
|
|
30
|
+
try {
|
|
31
|
+
const { appDeclaration } = await import(entry);
|
|
32
|
+
write(JSON.stringify(appDeclaration));
|
|
33
|
+
} catch (error) {
|
|
34
|
+
writeError((error instanceof Error ? error.message : "App definition evaluation failed").split("\\n")[0] + "\\n");
|
|
35
|
+
exit(1);
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
const evaluationFailureSchema = Schema.Struct({ stderr: Schema.String });
|
|
39
|
+
const evaluationFailure = (cause) => {
|
|
40
|
+
if (Schema.is(evaluationFailureSchema)(cause)) {
|
|
41
|
+
const message = cause.stderr.trim().split("\n")[0];
|
|
42
|
+
if (message)
|
|
43
|
+
return new Error(message);
|
|
44
|
+
}
|
|
45
|
+
return new Error("App definition evaluation failed before emitting a declaration");
|
|
46
|
+
};
|
|
47
|
+
async function appPackage(source, recipe) {
|
|
48
|
+
let directory = dirname(join(source.snapshotPath, recipe.entry));
|
|
49
|
+
for (;;) {
|
|
50
|
+
const file = join(directory, "package.json");
|
|
51
|
+
const present = await access(file).then(() => true, () => false);
|
|
52
|
+
if (present)
|
|
53
|
+
return {
|
|
54
|
+
directory,
|
|
55
|
+
metadata: Schema.decodeUnknownSync(packageSchema)(await readFile(file, "utf8")),
|
|
56
|
+
};
|
|
57
|
+
if (directory === source.snapshotPath)
|
|
58
|
+
return { directory, metadata: {} };
|
|
59
|
+
const parent = dirname(directory);
|
|
60
|
+
const path = relative(source.snapshotPath, parent);
|
|
61
|
+
if (path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path))
|
|
62
|
+
throw new Error("App entry is outside its captured source");
|
|
63
|
+
directory = parent;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Package entry policy pinned only by the captured source recipes that need it. */
|
|
67
|
+
export function appBackendPackageMainFields(recipe) {
|
|
68
|
+
return recipe.toolchain.packageResolution ===
|
|
69
|
+
"captured-browser-module-main-v1"
|
|
70
|
+
? ["browser", "module", "main"]
|
|
71
|
+
: undefined;
|
|
72
|
+
}
|
|
73
|
+
/** Output directory naming the Worker and declaration bundles of one build. */
|
|
74
|
+
const backendOutputDirectory = ".fungi-backend";
|
|
75
|
+
const declarationEntry = "fungi:app-declaration";
|
|
76
|
+
const declarationNamespace = "fungi-app-declaration";
|
|
77
|
+
const buildOnlySdk = "@fungi.computer/agar/backend-declaration";
|
|
78
|
+
const buildOnlySdkFile = /(?:^|\/)agar\/(?:src|dist)\/backend-declaration\.(?:js|ts)$/;
|
|
79
|
+
/** Metafile paths are relative to the build's working directory, with `/`. */
|
|
80
|
+
const metafilePath = (source, path) => relative(source.snapshotPath, path).split(sep).join("/");
|
|
81
|
+
/**
|
|
82
|
+
* Build one App backend and derive its declaration from that same build.
|
|
83
|
+
*
|
|
84
|
+
* A declared `fungi.backendDefinition` adds a second, build-only entry that
|
|
85
|
+
* derives the declaration with `buildAppDeclaration(appBackend, fungi.app)`.
|
|
86
|
+
* esbuild loads every path and resolves every import once per build, so the
|
|
87
|
+
* Worker serves exactly the definition bytes and module resolution the
|
|
88
|
+
* declaration describes. The Worker output is then checked to include that
|
|
89
|
+
* definition and no build-only declaration code, and the declaration output to
|
|
90
|
+
* import nothing at runtime.
|
|
91
|
+
*/
|
|
92
|
+
export async function createDefinitionEmission(source, recipe) {
|
|
93
|
+
const { directory, metadata } = await appPackage(source, recipe);
|
|
94
|
+
const app = metadata.fungi?.app;
|
|
95
|
+
const packageDeclaration = app === undefined
|
|
96
|
+
? emptyAppDeclaration
|
|
97
|
+
: Schema.decodeUnknownSync(appDeclarationSchema)({
|
|
98
|
+
handlers: [],
|
|
99
|
+
roles: [],
|
|
100
|
+
...app,
|
|
101
|
+
});
|
|
102
|
+
const definition = metadata.fungi?.backendDefinition;
|
|
103
|
+
let definitionPath = null;
|
|
104
|
+
if (definition !== undefined) {
|
|
105
|
+
definitionPath = await realpath(join(directory, definition));
|
|
106
|
+
const path = relative(await realpath(source.snapshotPath), definitionPath);
|
|
107
|
+
if (path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path))
|
|
108
|
+
throw new Error("App backend definition must be inside its captured source");
|
|
109
|
+
if (recipe.backend === null)
|
|
110
|
+
throw new Error("An App backend definition requires a backend build recipe");
|
|
111
|
+
}
|
|
112
|
+
const workerPath = join(source.snapshotPath, backendOutputDirectory, "worker.js");
|
|
113
|
+
const declarationPath = join(source.snapshotPath, backendOutputDirectory, "declaration.js");
|
|
114
|
+
let declarationCode;
|
|
115
|
+
const declarationPlugin = {
|
|
116
|
+
name: "derived-app-declaration",
|
|
117
|
+
setup(build) {
|
|
118
|
+
// Only the build's own declaration entry may load the derivation.
|
|
119
|
+
build.onResolve({ filter: /^fungi:app-declaration$/ }, (args) => args.kind === "entry-point"
|
|
120
|
+
? { path: "app-declaration.js", namespace: declarationNamespace }
|
|
121
|
+
: undefined);
|
|
122
|
+
build.onLoad({ filter: /.*/, namespace: declarationNamespace }, () => ({
|
|
123
|
+
contents: `import { appBackend } from ${JSON.stringify(definitionPath)}; import { buildAppDeclaration } from ${JSON.stringify(buildOnlySdk)}; export const appDeclaration = buildAppDeclaration(appBackend, ${JSON.stringify(packageDeclaration)});`,
|
|
124
|
+
resolveDir: directory,
|
|
125
|
+
loader: "js",
|
|
126
|
+
}));
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
/** Entries for the one backend build: the Worker and, if defined, its declaration. */
|
|
131
|
+
entryPoints: (entry) => definitionPath === null
|
|
132
|
+
? { worker: entry }
|
|
133
|
+
: { worker: entry, declaration: declarationEntry },
|
|
134
|
+
outdir: backendOutputDirectory,
|
|
135
|
+
plugins: definitionPath === null ? [] : [declarationPlugin],
|
|
136
|
+
/** Check one finished backend build and return its Worker output. */
|
|
137
|
+
worker(result) {
|
|
138
|
+
const metafile = result.metafile;
|
|
139
|
+
if (metafile === undefined)
|
|
140
|
+
throw new Error("App backend build did not return its metafile");
|
|
141
|
+
const outputs = result.outputFiles ?? [];
|
|
142
|
+
const expected = definitionPath === null ? [workerPath] : [workerPath, declarationPath];
|
|
143
|
+
const file = outputs.find((output) => output.path === workerPath);
|
|
144
|
+
if (file === undefined ||
|
|
145
|
+
outputs.length !== expected.length ||
|
|
146
|
+
!outputs.every((output) => expected.includes(output.path)))
|
|
147
|
+
throw new Error("App backend build must emit exactly its Worker and declaration");
|
|
148
|
+
const workerMeta = metafile.outputs[metafilePath(source, workerPath)];
|
|
149
|
+
if (workerMeta === undefined)
|
|
150
|
+
throw new Error("App backend build did not describe its Worker");
|
|
151
|
+
const inputs = Object.keys(workerMeta.inputs);
|
|
152
|
+
if (definitionPath === null)
|
|
153
|
+
return { file, inputs };
|
|
154
|
+
if (!inputs.includes(metafilePath(source, definitionPath)))
|
|
155
|
+
throw new Error("The Worker must import its declared fungi.backendDefinition; the declaration is derived from the definition it serves");
|
|
156
|
+
if (inputs.some((input) => input.startsWith(`${declarationNamespace}:`) ||
|
|
157
|
+
buildOnlySdkFile.test(input) ||
|
|
158
|
+
metafile.inputs[input]?.imports.some((imported) => imported.original === buildOnlySdk) === true))
|
|
159
|
+
throw new Error("The Worker must not import build-only App declaration code");
|
|
160
|
+
const declaration = outputs.find((output) => output.path === declarationPath);
|
|
161
|
+
const declarationMeta = metafile.outputs[metafilePath(source, declarationPath)];
|
|
162
|
+
if (declaration === undefined || declarationMeta === undefined)
|
|
163
|
+
throw new Error("App backend build did not derive its declaration");
|
|
164
|
+
const external = declarationMeta.imports.find((imported) => imported.external === true);
|
|
165
|
+
if (external !== undefined)
|
|
166
|
+
throw new Error(`App backend definition imports ${external.path}. Move runtime-specific imports into the Worker composition; keep the definition runtime-neutral.`);
|
|
167
|
+
if (declaration.contents.byteLength > recipe.maxOutputBytes)
|
|
168
|
+
throw new Error("App backend definition exceeds its pinned build bound");
|
|
169
|
+
declarationCode = declaration.text;
|
|
170
|
+
return { file, inputs };
|
|
171
|
+
},
|
|
172
|
+
/** Evaluate the declaration derived in the checked backend build. */
|
|
173
|
+
async declare(signal) {
|
|
174
|
+
if (definitionPath === null) {
|
|
175
|
+
if (packageDeclaration.api &&
|
|
176
|
+
Object.keys(packageDeclaration.api.methods).length > 0)
|
|
177
|
+
throw new Error("App API methods require fungi.backendDefinition; the build derives them from its methods instead of a handwritten table");
|
|
178
|
+
return packageDeclaration;
|
|
179
|
+
}
|
|
180
|
+
if (declarationCode === undefined)
|
|
181
|
+
throw new Error("The App declaration is derived from a checked backend build");
|
|
182
|
+
const file = join(source.snapshotPath, ".fungi-built-definition.mjs");
|
|
183
|
+
await writeFile(file, declarationCode, { flag: "wx", mode: 0o600 });
|
|
184
|
+
const result = await promisify(execFile)(process.execPath, [
|
|
185
|
+
"--input-type=module",
|
|
186
|
+
"--eval",
|
|
187
|
+
evaluateDefinition,
|
|
188
|
+
pathToFileURL(file).href,
|
|
189
|
+
], {
|
|
190
|
+
cwd: source.snapshotPath,
|
|
191
|
+
env: {},
|
|
192
|
+
maxBuffer: 1024 * 1024,
|
|
193
|
+
timeout: 30_000,
|
|
194
|
+
signal,
|
|
195
|
+
}).catch((cause) => {
|
|
196
|
+
throw evaluationFailure(cause);
|
|
197
|
+
});
|
|
198
|
+
return Schema.decodeUnknownSync(Schema.fromJsonString(appDeclarationSchema), { onExcessProperty: "error" })(result.stdout);
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { type AppId, type ArtifactRef, type AppReleaseCatalog, type Release, type ReleaseId, type Sha256Digest, type SourceRevision } from "@fungi.computer/puffball";
|
|
2
|
+
import type { AppDeclaration } from "@fungi.computer/puffball";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import type * as Scope from "effect/Scope";
|
|
6
|
+
import type { CandidateOutputManifest } from "./submission-contract.js";
|
|
7
|
+
/** Canonical recipe policy shared by core and the local adapter. */
|
|
8
|
+
export declare const pinnedRecipeSchema: Schema.Struct<{
|
|
9
|
+
readonly kind: Schema.optional<Schema.Literals<readonly ["bundled-html", "source-html"]>>;
|
|
10
|
+
readonly entry: Schema.String;
|
|
11
|
+
readonly assets: Schema.optional<Schema.Struct<{
|
|
12
|
+
readonly maxManifestBytes: Schema.Number;
|
|
13
|
+
readonly maxObjects: Schema.Number;
|
|
14
|
+
readonly maxObjectBytes: Schema.Number;
|
|
15
|
+
readonly maxTotalBytes: Schema.Number;
|
|
16
|
+
}>>;
|
|
17
|
+
readonly assetBase: Schema.optionalKey<Schema.String>;
|
|
18
|
+
readonly backend: Schema.Union<readonly [Schema.Null, Schema.Struct<{
|
|
19
|
+
readonly entry: Schema.optional<Schema.String>;
|
|
20
|
+
readonly runtime: Schema.Struct<{
|
|
21
|
+
readonly kind: Schema.Literal<"durable-facet">;
|
|
22
|
+
readonly stateSchema: Schema.Number;
|
|
23
|
+
}>;
|
|
24
|
+
readonly compatibilityDate: Schema.String;
|
|
25
|
+
readonly exportName: Schema.String;
|
|
26
|
+
}>]>;
|
|
27
|
+
readonly maxOutputBytes: Schema.Number;
|
|
28
|
+
}>;
|
|
29
|
+
/** Core configuration contains only immutable App identity and recipe policy. */
|
|
30
|
+
export declare const AppPublisherCoreConfigSchema: Schema.Struct<{
|
|
31
|
+
readonly appId: Schema.refine<AppId, Schema.String>;
|
|
32
|
+
readonly recipe: Schema.Struct<{
|
|
33
|
+
readonly kind: Schema.optional<Schema.Literals<readonly ["bundled-html", "source-html"]>>;
|
|
34
|
+
readonly entry: Schema.String;
|
|
35
|
+
readonly assets: Schema.optional<Schema.Struct<{
|
|
36
|
+
readonly maxManifestBytes: Schema.Number;
|
|
37
|
+
readonly maxObjects: Schema.Number;
|
|
38
|
+
readonly maxObjectBytes: Schema.Number;
|
|
39
|
+
readonly maxTotalBytes: Schema.Number;
|
|
40
|
+
}>>;
|
|
41
|
+
readonly assetBase: Schema.optionalKey<Schema.String>;
|
|
42
|
+
readonly backend: Schema.Union<readonly [Schema.Null, Schema.Struct<{
|
|
43
|
+
readonly entry: Schema.optional<Schema.String>;
|
|
44
|
+
readonly runtime: Schema.Struct<{
|
|
45
|
+
readonly kind: Schema.Literal<"durable-facet">;
|
|
46
|
+
readonly stateSchema: Schema.Number;
|
|
47
|
+
}>;
|
|
48
|
+
readonly compatibilityDate: Schema.String;
|
|
49
|
+
readonly exportName: Schema.String;
|
|
50
|
+
}>]>;
|
|
51
|
+
readonly maxOutputBytes: Schema.Number;
|
|
52
|
+
}>;
|
|
53
|
+
}>;
|
|
54
|
+
/** App identity and caller-selected recipe inputs before normalization. */
|
|
55
|
+
export type AppPublisherCoreConfigInput = Schema.Codec.Encoded<typeof AppPublisherCoreConfigSchema>;
|
|
56
|
+
/** Package-export conditions used by the pinned Workers builder. */
|
|
57
|
+
export declare const appBackendExportConditions: readonly string[];
|
|
58
|
+
/** Persisted, fully resolved recipe including its code-owned toolchain. */
|
|
59
|
+
export declare const capturedPinnedRecipeSchema: Schema.Struct<{
|
|
60
|
+
readonly kind: Schema.optional<Schema.Literals<readonly ["bundled-html", "source-html"]>>;
|
|
61
|
+
readonly bundle: Schema.Literal<true>;
|
|
62
|
+
readonly entry: Schema.String;
|
|
63
|
+
readonly format: Schema.Literals<readonly ["html", "esm", "assets"]>;
|
|
64
|
+
readonly assets: Schema.optional<Schema.Struct<{
|
|
65
|
+
readonly maxManifestBytes: Schema.Number;
|
|
66
|
+
readonly maxObjects: Schema.Number;
|
|
67
|
+
readonly maxObjectBytes: Schema.Number;
|
|
68
|
+
readonly maxTotalBytes: Schema.Number;
|
|
69
|
+
}>>;
|
|
70
|
+
readonly assetBase: Schema.optionalKey<Schema.String>;
|
|
71
|
+
readonly toolchain: Schema.$Record<Schema.String, Schema.String>;
|
|
72
|
+
readonly maxOutputBytes: Schema.Number;
|
|
73
|
+
readonly platform: Schema.Literals<readonly ["browser", "neutral"]>;
|
|
74
|
+
readonly backend: Schema.Union<readonly [Schema.Null, Schema.Struct<{
|
|
75
|
+
readonly entry: Schema.optional<Schema.String>;
|
|
76
|
+
readonly runtime: Schema.Struct<{
|
|
77
|
+
readonly kind: Schema.Literal<"durable-facet">;
|
|
78
|
+
readonly stateSchema: Schema.Number;
|
|
79
|
+
}>;
|
|
80
|
+
readonly compatibilityDate: Schema.String;
|
|
81
|
+
readonly exportName: Schema.String;
|
|
82
|
+
}>]>;
|
|
83
|
+
readonly target: Schema.Tuple<readonly [Schema.Literal<"es2022">]>;
|
|
84
|
+
}>;
|
|
85
|
+
/** Fully resolved recipe with its pinned toolchain. */
|
|
86
|
+
export type PinnedRecipe = Schema.Schema.Type<typeof capturedPinnedRecipeSchema>;
|
|
87
|
+
/** A source tree captured into a scoped local snapshot directory. */
|
|
88
|
+
export type CapturedSource = Readonly<{
|
|
89
|
+
readonly sourceRevision: SourceRevision;
|
|
90
|
+
readonly sourceTreeDigest: Sha256Digest;
|
|
91
|
+
readonly snapshotPath: string;
|
|
92
|
+
}>;
|
|
93
|
+
/** Compiler bytes or a bounded collection of emitted UI assets. */
|
|
94
|
+
export type RawBuildOutput = Readonly<{
|
|
95
|
+
readonly kind: "bytes";
|
|
96
|
+
readonly bytes: Uint8Array;
|
|
97
|
+
}> | Readonly<{
|
|
98
|
+
readonly kind: "assets";
|
|
99
|
+
readonly base: string;
|
|
100
|
+
readonly assets: readonly {
|
|
101
|
+
readonly path: string;
|
|
102
|
+
readonly bytes: Uint8Array;
|
|
103
|
+
}[];
|
|
104
|
+
}>;
|
|
105
|
+
/** A complete HTML document or an asset collection. */
|
|
106
|
+
export type UiBuildOutput = Readonly<{
|
|
107
|
+
readonly kind: "html";
|
|
108
|
+
readonly bytes: Uint8Array;
|
|
109
|
+
}> | Extract<RawBuildOutput, {
|
|
110
|
+
readonly kind: "assets";
|
|
111
|
+
}>;
|
|
112
|
+
/** Raw executor result before the recipe owner assigns App surface meaning. */
|
|
113
|
+
export type BuildExecutorOutput = RawBuildOutput | Readonly<{
|
|
114
|
+
readonly kind: "app";
|
|
115
|
+
readonly ui: PreparedUiOutput | null;
|
|
116
|
+
readonly backend: Uint8Array | null;
|
|
117
|
+
}>;
|
|
118
|
+
/** UI output paired with its immutable artifact media type. */
|
|
119
|
+
export type PreparedUiOutput = Readonly<{
|
|
120
|
+
readonly content: Extract<UiBuildOutput, {
|
|
121
|
+
readonly kind: "html";
|
|
122
|
+
}>;
|
|
123
|
+
readonly mediaType: "text/html; charset=utf-8";
|
|
124
|
+
}> | Readonly<{
|
|
125
|
+
readonly content: Extract<UiBuildOutput, {
|
|
126
|
+
readonly kind: "assets";
|
|
127
|
+
}>;
|
|
128
|
+
readonly mediaType: "application/vnd.fungi.app-assets+json";
|
|
129
|
+
}>;
|
|
130
|
+
/** Complete, non-empty App content accepted by immutable artifact writers. */
|
|
131
|
+
export type BuildOutput = (Readonly<{
|
|
132
|
+
readonly kind: "ui";
|
|
133
|
+
readonly ui: PreparedUiOutput;
|
|
134
|
+
}> | Readonly<{
|
|
135
|
+
readonly kind: "backend";
|
|
136
|
+
readonly backend: Uint8Array;
|
|
137
|
+
}> | Readonly<{
|
|
138
|
+
readonly kind: "app";
|
|
139
|
+
readonly ui: PreparedUiOutput;
|
|
140
|
+
readonly backend: Uint8Array;
|
|
141
|
+
}>) & Readonly<{
|
|
142
|
+
/** The source's declared request; absent means the App asks for nothing. */
|
|
143
|
+
readonly declaration?: AppDeclaration;
|
|
144
|
+
}>;
|
|
145
|
+
/** Reference and digest of the persisted App build root. */
|
|
146
|
+
export type StoredArtifact = Readonly<{
|
|
147
|
+
readonly artifactRef: ArtifactRef;
|
|
148
|
+
readonly artifactDigest: Sha256Digest;
|
|
149
|
+
}>;
|
|
150
|
+
/** A locally captured and built candidate awaiting native artifact/catalog custody. */
|
|
151
|
+
export type PreparedCandidate = Readonly<{
|
|
152
|
+
readonly appId: AppId;
|
|
153
|
+
readonly releaseId: ReleaseId;
|
|
154
|
+
readonly output: BuildOutput;
|
|
155
|
+
readonly provenance: Readonly<{
|
|
156
|
+
readonly sourceRevision: SourceRevision;
|
|
157
|
+
readonly sourceTreeDigest: Sha256Digest;
|
|
158
|
+
readonly buildRecipeDigest: Sha256Digest;
|
|
159
|
+
}>;
|
|
160
|
+
}>;
|
|
161
|
+
/** Four structural roles used by the private composition. */
|
|
162
|
+
export type SourceCapture = {
|
|
163
|
+
/** Acquire a snapshot whose cleanup follows the Effect scope. */
|
|
164
|
+
readonly capture: () => Effect.Effect<CapturedSource, unknown, Scope.Scope>;
|
|
165
|
+
};
|
|
166
|
+
/** Compiles captured source and reads its matching declaration. */
|
|
167
|
+
export type BuildExecutor = {
|
|
168
|
+
/** Compile only the captured source under its pinned recipe. */
|
|
169
|
+
readonly build: (source: CapturedSource, recipe: PinnedRecipe) => Effect.Effect<BuildExecutorOutput, unknown, Scope.Scope>;
|
|
170
|
+
/**
|
|
171
|
+
* Read the App's declaration from the same captured tree. Every App, a fork
|
|
172
|
+
* included, declares through its own source; there is no other source.
|
|
173
|
+
*/
|
|
174
|
+
readonly declaration?: (source: CapturedSource, recipe: PinnedRecipe) => Effect.Effect<AppDeclaration, unknown>;
|
|
175
|
+
};
|
|
176
|
+
/** Persists one complete App build as immutable artifacts. */
|
|
177
|
+
export type ArtifactWriter = {
|
|
178
|
+
/** Persist the App root and return its reference and digest. */
|
|
179
|
+
readonly write: (output: BuildOutput) => Effect.Effect<StoredArtifact, unknown>;
|
|
180
|
+
};
|
|
181
|
+
/** Release catalog role needed to record a built candidate. */
|
|
182
|
+
export type ReleaseCreator = Pick<AppReleaseCatalog, "createRelease">;
|
|
183
|
+
/** Captured build inputs and the resource owners used to publish a candidate. */
|
|
184
|
+
export type AppPublisherOptions = Readonly<{
|
|
185
|
+
readonly config: AppPublisherCoreConfigInput;
|
|
186
|
+
readonly releaseCatalog: ReleaseCreator;
|
|
187
|
+
readonly sourceCapture: SourceCapture;
|
|
188
|
+
readonly buildExecutor: BuildExecutor;
|
|
189
|
+
readonly artifactWriter: ArtifactWriter;
|
|
190
|
+
}>;
|
|
191
|
+
/** Builds and records immutable App Release candidates. */
|
|
192
|
+
export type AppPublisher = Readonly<{
|
|
193
|
+
/** Capture, build, store, attest, and create exactly one candidate. */
|
|
194
|
+
readonly createCandidate: (releaseId: ReleaseId) => Effect.Effect<Release, AppPublisherError>;
|
|
195
|
+
}>;
|
|
196
|
+
/** Builds a candidate before artifact storage and catalog publication. */
|
|
197
|
+
export type AppCandidatePreparer = Readonly<{
|
|
198
|
+
/** Capture and build once; persistence remains with the resource-native host. */
|
|
199
|
+
readonly prepareCandidate: (releaseId: ReleaseId) => Effect.Effect<PreparedCandidate, AppPublisherError>;
|
|
200
|
+
}>;
|
|
201
|
+
/** The bounded stages reported by AppPublisherError. */
|
|
202
|
+
export type AppPublisherStage = "configuration" | "source" | "build" | "artifact" | "release";
|
|
203
|
+
/** Closed failure boundary for the four private composition stages. */
|
|
204
|
+
export declare class AppPublisherError extends Error {
|
|
205
|
+
readonly stage: AppPublisherStage;
|
|
206
|
+
readonly _tag: "AppPublisherError";
|
|
207
|
+
constructor(stage: AppPublisherStage, options?: ErrorOptions);
|
|
208
|
+
}
|
|
209
|
+
/** App identity and normalized recipe with its digest. */
|
|
210
|
+
export type CapturedConfig = Readonly<{
|
|
211
|
+
readonly appId: AppId;
|
|
212
|
+
readonly recipe: PinnedRecipe;
|
|
213
|
+
readonly buildRecipeDigest: Sha256Digest;
|
|
214
|
+
}>;
|
|
215
|
+
/**
|
|
216
|
+
* Whether a pinned recipe pins this Publisher's current toolchain (its build
|
|
217
|
+
* policy included). An executor refuses any other: a recipe pinned under
|
|
218
|
+
* another policy describes a build this code does not perform.
|
|
219
|
+
*/
|
|
220
|
+
export declare function pinsCurrentToolchain(recipe: PinnedRecipe): boolean;
|
|
221
|
+
/** Hash every captured build and output-policy input in the Publisher recipe. */
|
|
222
|
+
export declare function digestPinnedRecipe(recipe: PinnedRecipe): Sha256Digest;
|
|
223
|
+
/** Decode and normalize Publisher-owned recipe policy for durable seed proof. */
|
|
224
|
+
export declare function normalizeAppPublisherRecipe(input: {
|
|
225
|
+
appId: string;
|
|
226
|
+
recipe: AppPublisherCoreConfigInput["recipe"];
|
|
227
|
+
}): Readonly<{
|
|
228
|
+
recipe: PinnedRecipe;
|
|
229
|
+
buildRecipeDigest: Sha256Digest;
|
|
230
|
+
}>;
|
|
231
|
+
/**
|
|
232
|
+
* The largest single object a build under this recipe may upload: no larger
|
|
233
|
+
* than the largest output bound the recipe could assign it under.
|
|
234
|
+
*/
|
|
235
|
+
export declare function pinnedRecipeObjectBound(recipe: PinnedRecipe): number;
|
|
236
|
+
/**
|
|
237
|
+
* Whether output built elsewhere (a Computer's candidate manifest) is one
|
|
238
|
+
* this recipe could assign: the same surfaces and `maxOutputBytes` bound the
|
|
239
|
+
* preparer applies here. Asset sizes stay the artifact writer's policy.
|
|
240
|
+
*/
|
|
241
|
+
export declare function fitsPinnedRecipe(output: CandidateOutputManifest, recipe: PinnedRecipe): boolean;
|
|
242
|
+
/** Construct one private App Publisher with copied core configuration and roles. */
|
|
243
|
+
export declare function createAppCandidatePreparer(options: Pick<AppPublisherOptions, "config" | "sourceCapture" | "buildExecutor">): AppCandidatePreparer;
|
|
244
|
+
/** Prepare a candidate from a previously captured recipe. */
|
|
245
|
+
export declare function createCapturedCandidatePreparer(config: CapturedConfig, options: Pick<AppPublisherOptions, "sourceCapture" | "buildExecutor">): AppCandidatePreparer;
|
|
246
|
+
/** Persist a prepared build through the native artifact and App Release owners. */
|
|
247
|
+
export declare function commitPreparedCandidate(prepared: PreparedCandidate, options: Pick<AppPublisherOptions, "artifactWriter" | "releaseCatalog">): Effect.Effect<Release, AppPublisherError>;
|
|
248
|
+
/** Compose capture, compilation, immutable writing and Release recording. */
|
|
249
|
+
export declare function createAppPublisher(options: AppPublisherOptions): AppPublisher;
|