@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,10 @@
|
|
|
1
|
+
/** App source seed receipts, seed claims and the fork algorithm, owned
|
|
2
|
+
* generically here; the Publisher supplies D1 adapters and the retained Git
|
|
3
|
+
* admission transport. The operator's Node-only template push (it runs the
|
|
4
|
+
* `git` executable) is deliberately not re-exported here, so Worker bundles
|
|
5
|
+
* never import it: use `@fungi.computer/oyster/source/template-push`. */
|
|
6
|
+
export { templateRepositoryForApp } from "./template-repository.js";
|
|
7
|
+
export { seedReceiptInputSchema, sameReceipt, type PublisherTemplateSeedReceipt, type PublisherTemplateSeedReceipts, } from "./seed-receipt.js";
|
|
8
|
+
export { readTeamAppSeedProvenance, privateTeamBuildSource, TeamAppSourceError, } from "./team-source.js";
|
|
9
|
+
export { parseTemplateSeedClaim, recordTemplateSeed, templateSeedClaimSchema, TEMPLATE_SEED_CLAIM_MAX_BYTES, type TemplateSeedClaim, } from "./template-seed.js";
|
|
10
|
+
export { forkAppSource, type AppSourceForkResult } from "./fork.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** App source seed receipts, seed claims and the fork algorithm, owned
|
|
2
|
+
* generically here; the Publisher supplies D1 adapters and the retained Git
|
|
3
|
+
* admission transport. The operator's Node-only template push (it runs the
|
|
4
|
+
* `git` executable) is deliberately not re-exported here, so Worker bundles
|
|
5
|
+
* never import it: use `@fungi.computer/oyster/source/template-push`. */
|
|
6
|
+
export { templateRepositoryForApp } from "./template-repository.js";
|
|
7
|
+
export { seedReceiptInputSchema, sameReceipt, } from "./seed-receipt.js";
|
|
8
|
+
export { readTeamAppSeedProvenance, privateTeamBuildSource, TeamAppSourceError, } from "./team-source.js";
|
|
9
|
+
export { parseTemplateSeedClaim, recordTemplateSeed, templateSeedClaimSchema, TEMPLATE_SEED_CLAIM_MAX_BYTES, } from "./template-seed.js";
|
|
10
|
+
export { forkAppSource } from "./fork.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { appIdSchema, sha256DigestSchema, sourceRevisionSchema } from "@fungi.computer/puffball/effect";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import { type PinnedRecipe } from "../build-contract.js";
|
|
4
|
+
export declare const seedReceiptInputSchema: Schema.Struct<{
|
|
5
|
+
readonly appId: Schema.refine<import("@fungi.computer/puffball").AppId, Schema.String>;
|
|
6
|
+
readonly sourceRevision: Schema.refine<import("@fungi.computer/puffball").SourceRevision, Schema.String>;
|
|
7
|
+
readonly sourceTreeDigest: Schema.refine<import("@fungi.computer/puffball").Sha256Digest, Schema.String>;
|
|
8
|
+
readonly seedRevision: Schema.refine<import("@fungi.computer/puffball").SourceRevision, Schema.String>;
|
|
9
|
+
readonly recipe: Schema.Struct<{
|
|
10
|
+
readonly kind: Schema.optional<Schema.Literals<readonly ["bundled-html", "source-html"]>>;
|
|
11
|
+
readonly bundle: Schema.Literal<true>;
|
|
12
|
+
readonly entry: Schema.String;
|
|
13
|
+
readonly format: Schema.Literals<readonly ["html", "esm", "assets"]>;
|
|
14
|
+
readonly assets: Schema.optional<Schema.Struct<{
|
|
15
|
+
readonly maxManifestBytes: Schema.Number;
|
|
16
|
+
readonly maxObjects: Schema.Number;
|
|
17
|
+
readonly maxObjectBytes: Schema.Number;
|
|
18
|
+
readonly maxTotalBytes: Schema.Number;
|
|
19
|
+
}>>;
|
|
20
|
+
readonly assetBase: Schema.optionalKey<Schema.String>;
|
|
21
|
+
readonly toolchain: Schema.$Record<Schema.String, Schema.String>;
|
|
22
|
+
readonly maxOutputBytes: Schema.Number;
|
|
23
|
+
readonly platform: Schema.Literals<readonly ["browser", "neutral"]>;
|
|
24
|
+
readonly backend: Schema.Union<readonly [Schema.Null, Schema.Struct<{
|
|
25
|
+
readonly entry: Schema.optional<Schema.String>;
|
|
26
|
+
readonly runtime: Schema.Struct<{
|
|
27
|
+
readonly kind: Schema.Literal<"durable-facet">;
|
|
28
|
+
readonly stateSchema: Schema.Number;
|
|
29
|
+
}>;
|
|
30
|
+
readonly compatibilityDate: Schema.String;
|
|
31
|
+
readonly exportName: Schema.String;
|
|
32
|
+
}>]>;
|
|
33
|
+
readonly target: Schema.Tuple<readonly [Schema.Literal<"es2022">]>;
|
|
34
|
+
}>;
|
|
35
|
+
readonly buildRecipeDigest: Schema.refine<import("@fungi.computer/puffball").Sha256Digest, Schema.String>;
|
|
36
|
+
}>;
|
|
37
|
+
export type PublisherTemplateSeedReceipt = Readonly<{
|
|
38
|
+
appId: Schema.Schema.Type<typeof appIdSchema>;
|
|
39
|
+
repository: string;
|
|
40
|
+
sourceRevision: Schema.Schema.Type<typeof sourceRevisionSchema>;
|
|
41
|
+
sourceTreeDigest: Schema.Schema.Type<typeof sha256DigestSchema>;
|
|
42
|
+
seedRevision: Schema.Schema.Type<typeof sourceRevisionSchema>;
|
|
43
|
+
recipe: PinnedRecipe;
|
|
44
|
+
buildRecipeDigest: Schema.Schema.Type<typeof sha256DigestSchema>;
|
|
45
|
+
}>;
|
|
46
|
+
/** Append-only proof that an exact projected template was seeded, keyed by
|
|
47
|
+
* the template's repository and recorded seed revision. The Publisher's D1
|
|
48
|
+
* adapter is the only durable implementation; this is the port it fulfills. */
|
|
49
|
+
export type PublisherTemplateSeedReceipts = Readonly<{
|
|
50
|
+
read(appId: PublisherTemplateSeedReceipt["appId"], seedRevision: PublisherTemplateSeedReceipt["seedRevision"]): Promise<PublisherTemplateSeedReceipt | null>;
|
|
51
|
+
record(input: Schema.Schema.Type<typeof seedReceiptInputSchema>): Promise<PublisherTemplateSeedReceipt>;
|
|
52
|
+
}>;
|
|
53
|
+
export declare const sameReceipt: (left: PublisherTemplateSeedReceipt, right: PublisherTemplateSeedReceipt) => boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { appIdSchema, sha256DigestSchema, sourceRevisionSchema, } from "@fungi.computer/puffball/effect";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import { capturedPinnedRecipeSchema, } from "../build-contract.js";
|
|
4
|
+
export const seedReceiptInputSchema = Schema.Struct({
|
|
5
|
+
appId: appIdSchema,
|
|
6
|
+
sourceRevision: sourceRevisionSchema,
|
|
7
|
+
sourceTreeDigest: sha256DigestSchema,
|
|
8
|
+
seedRevision: sourceRevisionSchema,
|
|
9
|
+
recipe: capturedPinnedRecipeSchema,
|
|
10
|
+
buildRecipeDigest: sha256DigestSchema,
|
|
11
|
+
});
|
|
12
|
+
export const sameReceipt = (left, right) => left.appId === right.appId &&
|
|
13
|
+
left.repository === right.repository &&
|
|
14
|
+
left.sourceRevision === right.sourceRevision &&
|
|
15
|
+
left.sourceTreeDigest === right.sourceTreeDigest &&
|
|
16
|
+
left.seedRevision === right.seedRevision &&
|
|
17
|
+
JSON.stringify(left.recipe) === JSON.stringify(right.recipe) &&
|
|
18
|
+
left.buildRecipeDigest === right.buildRecipeDigest;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { TeamAppPublisherRegistry } from "@fungi.computer/puffball/publisher";
|
|
2
|
+
import type { PublisherTemplateSeedReceipt } from "./seed-receipt.js";
|
|
3
|
+
export declare class TeamAppSourceError extends Error {
|
|
4
|
+
readonly code: "denied" | "unavailable";
|
|
5
|
+
constructor(code: "denied" | "unavailable");
|
|
6
|
+
}
|
|
7
|
+
export declare function readTeamAppSeedProvenance(options: {
|
|
8
|
+
appId: string;
|
|
9
|
+
teamId: string;
|
|
10
|
+
registry: TeamAppPublisherRegistry;
|
|
11
|
+
readSeedReceipt(appId: PublisherTemplateSeedReceipt["appId"], seedRevision: PublisherTemplateSeedReceipt["seedRevision"]): Promise<PublisherTemplateSeedReceipt | null>;
|
|
12
|
+
}): Promise<Readonly<{
|
|
13
|
+
appId: string;
|
|
14
|
+
repository: string;
|
|
15
|
+
templateSeed: Readonly<{
|
|
16
|
+
appId: string;
|
|
17
|
+
revision: string;
|
|
18
|
+
}>;
|
|
19
|
+
recipe: PublisherTemplateSeedReceipt["recipe"];
|
|
20
|
+
buildRecipeDigest: PublisherTemplateSeedReceipt["buildRecipeDigest"];
|
|
21
|
+
}>>;
|
|
22
|
+
/** A fork keeps the seed's exact builder; private UI assets use Vite's relative base. */
|
|
23
|
+
export declare function privateTeamBuildSource(source: Awaited<ReturnType<typeof readTeamAppSeedProvenance>>): Readonly<{
|
|
24
|
+
appId: string;
|
|
25
|
+
repository: string;
|
|
26
|
+
templateSeed: Readonly<{
|
|
27
|
+
appId: string;
|
|
28
|
+
revision: string;
|
|
29
|
+
}>;
|
|
30
|
+
recipe: PublisherTemplateSeedReceipt["recipe"];
|
|
31
|
+
buildRecipeDigest: PublisherTemplateSeedReceipt["buildRecipeDigest"];
|
|
32
|
+
}>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { parseAppId } from "@fungi.computer/puffball";
|
|
2
|
+
import { digestPinnedRecipe } from "../build-contract.js";
|
|
3
|
+
import { templateRepositoryForApp } from "./template-repository.js";
|
|
4
|
+
export class TeamAppSourceError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code) {
|
|
7
|
+
super(`Team App source ${code}`);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "TeamAppSourceError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function readTeamAppSeedProvenance(options) {
|
|
13
|
+
const appId = parseAppId(options.appId);
|
|
14
|
+
const authorization = await options.registry.authorize(appId, options.teamId);
|
|
15
|
+
if (authorization.kind !== "authorized")
|
|
16
|
+
throw new TeamAppSourceError("denied");
|
|
17
|
+
const registration = authorization.registration;
|
|
18
|
+
const templateSeed = registration.templateSeed;
|
|
19
|
+
if (registration.source.provider !== "artifacts" ||
|
|
20
|
+
templateSeed === undefined)
|
|
21
|
+
throw new TeamAppSourceError("unavailable");
|
|
22
|
+
const receipt = await options.readSeedReceipt(parseAppId(templateSeed.appId), templateSeed.revision);
|
|
23
|
+
if (receipt?.appId !== templateSeed.appId ||
|
|
24
|
+
receipt.seedRevision !== templateSeed.revision ||
|
|
25
|
+
receipt.repository !== templateRepositoryForApp(templateSeed.appId) ||
|
|
26
|
+
digestPinnedRecipe(receipt.recipe) !== receipt.buildRecipeDigest)
|
|
27
|
+
throw new TeamAppSourceError("unavailable");
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
appId,
|
|
30
|
+
repository: registration.source.repository,
|
|
31
|
+
templateSeed,
|
|
32
|
+
recipe: receipt.recipe,
|
|
33
|
+
buildRecipeDigest: receipt.buildRecipeDigest,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** A fork keeps the seed's exact builder; private UI assets use Vite's relative base. */
|
|
37
|
+
export function privateTeamBuildSource(source) {
|
|
38
|
+
if (source.recipe.format !== "assets")
|
|
39
|
+
return source;
|
|
40
|
+
const recipe = { ...source.recipe, assetBase: "./" };
|
|
41
|
+
return { ...source, recipe, buildRecipeDigest: digestPinnedRecipe(recipe) };
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type SourceRevision } from "@fungi.computer/puffball";
|
|
2
|
+
/** One projected source tree, as `createFirstPartyAppSourceDistribution` returns it. */
|
|
3
|
+
export type TemplateSourceFiles = readonly Readonly<{
|
|
4
|
+
path: string;
|
|
5
|
+
contents: Uint8Array;
|
|
6
|
+
}>[];
|
|
7
|
+
/** What one template push did: the template's `main` now names `seedRevision`. */
|
|
8
|
+
export type TemplatePushResult = Readonly<{
|
|
9
|
+
seedRevision: SourceRevision;
|
|
10
|
+
/** False when `main` already held this exact tree (an exact retry). */
|
|
11
|
+
pushed: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Commit one projected App source tree onto its template repository's `main`
|
|
15
|
+
* with ordinary Git, and push it there over smart HTTP.
|
|
16
|
+
*
|
|
17
|
+
* The tree holds exactly the given bytes (no attributes or filters), each a
|
|
18
|
+
* regular non-executable file. The first seed is a parentless commit; a later
|
|
19
|
+
* different tree is an ordinary fast-forward on the current head; an exact
|
|
20
|
+
* retry pushes nothing and answers the current head. Nothing is forced. The
|
|
21
|
+
* `headers` authenticate every request and reach Git only through its config
|
|
22
|
+
* environment.
|
|
23
|
+
*/
|
|
24
|
+
export declare function pushTemplateSource(input: Readonly<{
|
|
25
|
+
appId: string;
|
|
26
|
+
/** The upstream commit the tree was projected from (`git:<sha>`). */
|
|
27
|
+
sourceRevision: string;
|
|
28
|
+
files: TemplateSourceFiles;
|
|
29
|
+
/** The template repository's smart-HTTP URL, without credentials. */
|
|
30
|
+
remote: string;
|
|
31
|
+
headers: Readonly<Record<string, string>>;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}>): Promise<TemplatePushResult>;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { parseAppId, parseSourceRevision, } from "@fungi.computer/puffball";
|
|
6
|
+
import { authenticatedGitEnvironment, repositoryEnvironment, } from "../build/git-environment.js";
|
|
7
|
+
/** The fixed identity of every seed commit, so a retry of the same tree on
|
|
8
|
+
* the same parent is the same commit. */
|
|
9
|
+
const seedIdentity = {
|
|
10
|
+
GIT_AUTHOR_NAME: "Botanical App Source",
|
|
11
|
+
GIT_AUTHOR_EMAIL: "app-source@botanical.invalid",
|
|
12
|
+
GIT_AUTHOR_DATE: "@0 +0000",
|
|
13
|
+
GIT_COMMITTER_NAME: "Botanical App Source",
|
|
14
|
+
GIT_COMMITTER_EMAIL: "app-source@botanical.invalid",
|
|
15
|
+
GIT_COMMITTER_DATE: "@0 +0000",
|
|
16
|
+
};
|
|
17
|
+
/** Artifacts' native fork drops delta-encoded objects, so seed packs carry
|
|
18
|
+
* whole objects only. Every local object is loose (written here, or fetched
|
|
19
|
+
* with an unpack limit no fetch reaches), so no delta exists to reuse; a zero
|
|
20
|
+
* window computes none, `--no-thin` sends none against remote objects and no
|
|
21
|
+
* pack is reused verbatim. */
|
|
22
|
+
const wholeObjectPush = [
|
|
23
|
+
"-c",
|
|
24
|
+
"pack.window=0",
|
|
25
|
+
"-c",
|
|
26
|
+
"pack.depth=0",
|
|
27
|
+
"-c",
|
|
28
|
+
"pack.allowPackReuse=false",
|
|
29
|
+
];
|
|
30
|
+
const looseFetch = ["-c", "fetch.unpackLimit=1000000000"];
|
|
31
|
+
const objectId = /^[0-9a-f]{40}$/u;
|
|
32
|
+
/** One whole publication (project, fetch, push, confirm) is bounded. */
|
|
33
|
+
const TEMPLATE_PUSH_TIMEOUT_MS = 10 * 60_000;
|
|
34
|
+
/** The Git subcommand of `args`, past any leading `-c name=value` pairs. */
|
|
35
|
+
function command(args) {
|
|
36
|
+
let at = 0;
|
|
37
|
+
while (args[at] === "-c")
|
|
38
|
+
at += 2;
|
|
39
|
+
return args[at] ?? "";
|
|
40
|
+
}
|
|
41
|
+
function runGit(args, environment, signal, input) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const child = spawn("git", [...args], {
|
|
44
|
+
env: environment,
|
|
45
|
+
shell: false,
|
|
46
|
+
signal,
|
|
47
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
48
|
+
});
|
|
49
|
+
let stdout = "";
|
|
50
|
+
let stderr = "";
|
|
51
|
+
let spawnError;
|
|
52
|
+
child.stdout.setEncoding("utf8");
|
|
53
|
+
child.stderr.setEncoding("utf8");
|
|
54
|
+
child.stdout.on("data", (chunk) => {
|
|
55
|
+
stdout += chunk;
|
|
56
|
+
});
|
|
57
|
+
child.stderr.on("data", (chunk) => {
|
|
58
|
+
stderr += chunk;
|
|
59
|
+
});
|
|
60
|
+
child.once("error", (error) => {
|
|
61
|
+
spawnError = error;
|
|
62
|
+
});
|
|
63
|
+
child.once("close", (code) => {
|
|
64
|
+
if (code === 0 && spawnError === undefined) {
|
|
65
|
+
resolve(stdout);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const detail = stderr.trim();
|
|
69
|
+
reject(spawnError ??
|
|
70
|
+
new Error(`git ${command(args)} failed${detail ? `: ${detail}` : ` (${String(code)})`}`));
|
|
71
|
+
});
|
|
72
|
+
child.stdin.end(input);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** A template path names one regular file below the root: relative, no empty,
|
|
76
|
+
* `.`, `..` or `.git` part, and never both a file and a directory. */
|
|
77
|
+
function checkTemplatePaths(files) {
|
|
78
|
+
const paths = new Set();
|
|
79
|
+
for (const { path } of files) {
|
|
80
|
+
const parts = path.split("/");
|
|
81
|
+
if (path.length > 4096 ||
|
|
82
|
+
path.includes("\\") ||
|
|
83
|
+
path.includes("\0") ||
|
|
84
|
+
parts.some((part) => part === "" ||
|
|
85
|
+
part === "." ||
|
|
86
|
+
part === ".." ||
|
|
87
|
+
part.toLowerCase() === ".git"))
|
|
88
|
+
throw new Error("Template source path is invalid");
|
|
89
|
+
if (paths.has(path))
|
|
90
|
+
throw new Error("Duplicate template source path");
|
|
91
|
+
paths.add(path);
|
|
92
|
+
}
|
|
93
|
+
for (const path of paths) {
|
|
94
|
+
const parts = path.split("/");
|
|
95
|
+
for (let end = 1; end < parts.length; end += 1)
|
|
96
|
+
if (paths.has(parts.slice(0, end).join("/")))
|
|
97
|
+
throw new Error("Template source path is both a file and a directory");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function oid(output) {
|
|
101
|
+
const value = output.trim();
|
|
102
|
+
if (!objectId.test(value))
|
|
103
|
+
throw new Error("git returned no object id");
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
function remoteMain(output) {
|
|
107
|
+
for (const line of output.split("\n")) {
|
|
108
|
+
const [hash, ref] = line.split("\t");
|
|
109
|
+
if (ref === "refs/heads/main" && hash !== undefined && objectId.test(hash))
|
|
110
|
+
return hash;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Commit one projected App source tree onto its template repository's `main`
|
|
116
|
+
* with ordinary Git, and push it there over smart HTTP.
|
|
117
|
+
*
|
|
118
|
+
* The tree holds exactly the given bytes (no attributes or filters), each a
|
|
119
|
+
* regular non-executable file. The first seed is a parentless commit; a later
|
|
120
|
+
* different tree is an ordinary fast-forward on the current head; an exact
|
|
121
|
+
* retry pushes nothing and answers the current head. Nothing is forced. The
|
|
122
|
+
* `headers` authenticate every request and reach Git only through its config
|
|
123
|
+
* environment.
|
|
124
|
+
*/
|
|
125
|
+
export async function pushTemplateSource(input) {
|
|
126
|
+
const appId = parseAppId(input.appId);
|
|
127
|
+
const sourceRevision = parseSourceRevision(input.sourceRevision);
|
|
128
|
+
const remote = new URL(input.remote);
|
|
129
|
+
if (remote.username || remote.password || remote.search || remote.hash)
|
|
130
|
+
throw new Error("Template remote must not carry credentials or a query");
|
|
131
|
+
if (input.files.length === 0)
|
|
132
|
+
throw new Error("Template source is empty");
|
|
133
|
+
checkTemplatePaths(input.files);
|
|
134
|
+
const deadline = AbortSignal.timeout(TEMPLATE_PUSH_TIMEOUT_MS);
|
|
135
|
+
const signal = input.signal === undefined
|
|
136
|
+
? deadline
|
|
137
|
+
: AbortSignal.any([input.signal, deadline]);
|
|
138
|
+
const work = await mkdtemp(join(tmpdir(), "oyster-template-push-"));
|
|
139
|
+
try {
|
|
140
|
+
const gitDir = join(work, "repository.git");
|
|
141
|
+
const staging = join(work, "files");
|
|
142
|
+
const local = {
|
|
143
|
+
...repositoryEnvironment(),
|
|
144
|
+
HOME: work,
|
|
145
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
146
|
+
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
147
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
148
|
+
GIT_DIR: gitDir,
|
|
149
|
+
GIT_INDEX_FILE: join(work, "index"),
|
|
150
|
+
};
|
|
151
|
+
const network = {
|
|
152
|
+
...authenticatedGitEnvironment(input.headers),
|
|
153
|
+
HOME: work,
|
|
154
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
155
|
+
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
156
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
157
|
+
GIT_DIR: gitDir,
|
|
158
|
+
};
|
|
159
|
+
const git = (args, stdin) => runGit(args, local, signal, stdin);
|
|
160
|
+
const remoteGit = (args) => runGit(args, network, signal);
|
|
161
|
+
await runGit(["init", "--quiet", "--bare", "--initial-branch=main", gitDir], local, signal);
|
|
162
|
+
// Blobs: raw bytes, loose objects, no clean/smudge or eol filters.
|
|
163
|
+
await mkdir(staging);
|
|
164
|
+
const paths = [];
|
|
165
|
+
for (const [at, file] of input.files.entries()) {
|
|
166
|
+
const path = join(staging, String(at));
|
|
167
|
+
await writeFile(path, file.contents);
|
|
168
|
+
paths.push(path);
|
|
169
|
+
}
|
|
170
|
+
const blobs = (await git(["hash-object", "-w", "--no-filters", "--stdin-paths"], `${paths.join("\n")}\n`))
|
|
171
|
+
.trim()
|
|
172
|
+
.split("\n");
|
|
173
|
+
if (blobs.length !== input.files.length ||
|
|
174
|
+
!blobs.every((b) => objectId.test(b)))
|
|
175
|
+
throw new Error("git did not write every template blob");
|
|
176
|
+
const index = input.files
|
|
177
|
+
.map((file, at) => `100644 ${blobs[at] ?? ""}\t${file.path}\0`)
|
|
178
|
+
.join("");
|
|
179
|
+
await git(["update-index", "-z", "--add", "--index-info"], index);
|
|
180
|
+
const tree = oid(await git(["write-tree"]));
|
|
181
|
+
const head = remoteMain(await remoteGit(["ls-remote", input.remote, "refs/heads/main"]));
|
|
182
|
+
let parents = [];
|
|
183
|
+
if (head !== null) {
|
|
184
|
+
await remoteGit([
|
|
185
|
+
...looseFetch,
|
|
186
|
+
"fetch",
|
|
187
|
+
"--quiet",
|
|
188
|
+
"--depth=1",
|
|
189
|
+
"--no-tags",
|
|
190
|
+
input.remote,
|
|
191
|
+
"refs/heads/main",
|
|
192
|
+
]);
|
|
193
|
+
if (oid(await git(["rev-parse", `${head}^{tree}`])) === tree)
|
|
194
|
+
return {
|
|
195
|
+
seedRevision: parseSourceRevision(`git:${head}`),
|
|
196
|
+
pushed: false,
|
|
197
|
+
};
|
|
198
|
+
parents = ["-p", head];
|
|
199
|
+
}
|
|
200
|
+
const commit = oid(await runGit([
|
|
201
|
+
"commit-tree",
|
|
202
|
+
tree,
|
|
203
|
+
...parents,
|
|
204
|
+
"-m",
|
|
205
|
+
`Seed ${appId} from ${sourceRevision}`,
|
|
206
|
+
], { ...local, ...seedIdentity }, signal));
|
|
207
|
+
await remoteGit([
|
|
208
|
+
...wholeObjectPush,
|
|
209
|
+
"push",
|
|
210
|
+
"--quiet",
|
|
211
|
+
"--no-thin",
|
|
212
|
+
input.remote,
|
|
213
|
+
`${commit}:refs/heads/main`,
|
|
214
|
+
]);
|
|
215
|
+
// A successful push is not sufficient: main must now name this commit.
|
|
216
|
+
if (remoteMain(await remoteGit(["ls-remote", input.remote, "refs/heads/main"])) !== commit)
|
|
217
|
+
throw new Error("Template main moved during the push");
|
|
218
|
+
return { seedRevision: parseSourceRevision(`git:${commit}`), pushed: true };
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
await rm(work, { recursive: true, force: true });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Derive the private editable source repository from its validated App identity. */
|
|
2
|
+
export declare function templateRepositoryForApp(appId: string): string;
|
|
3
|
+
/** The App whose template `repository` is, or null when it names none. */
|
|
4
|
+
export declare function appForTemplateRepository(repository: string): string | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { parseAppId } from "@fungi.computer/puffball";
|
|
2
|
+
import { appIdSchema } from "@fungi.computer/puffball/effect";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
const TEMPLATE_REPOSITORY_PREFIX = "template-";
|
|
6
|
+
/** Derive the private editable source repository from its validated App identity. */
|
|
7
|
+
export function templateRepositoryForApp(appId) {
|
|
8
|
+
const parsed = parseAppId(appId);
|
|
9
|
+
// AppId's maximum 63-character suffix makes this repository at most 72 chars.
|
|
10
|
+
return `${TEMPLATE_REPOSITORY_PREFIX}${parsed.slice("app_".length)}`;
|
|
11
|
+
}
|
|
12
|
+
/** The App whose template `repository` is, or null when it names none. */
|
|
13
|
+
export function appForTemplateRepository(repository) {
|
|
14
|
+
if (!repository.startsWith(TEMPLATE_REPOSITORY_PREFIX))
|
|
15
|
+
return null;
|
|
16
|
+
const appId = Schema.decodeUnknownOption(appIdSchema)(`app_${repository.slice(TEMPLATE_REPOSITORY_PREFIX.length)}`);
|
|
17
|
+
return Option.isSome(appId) &&
|
|
18
|
+
templateRepositoryForApp(appId.value) === repository
|
|
19
|
+
? appId.value
|
|
20
|
+
: null;
|
|
21
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import type { Artifacts } from "@fungi.computer/burrow";
|
|
3
|
+
import type { PublisherTemplateSeedReceipts } from "./seed-receipt.js";
|
|
4
|
+
/** A seed claim is a few fields and one pinned recipe; the source itself
|
|
5
|
+
* arrived by `git push`. */
|
|
6
|
+
export declare const TEMPLATE_SEED_CLAIM_MAX_BYTES: number;
|
|
7
|
+
/** The operator's claim after pushing a projected source commit to the App's
|
|
8
|
+
* template: that `seedRevision` is that commit, projected from
|
|
9
|
+
* `sourceRevision`, and builds with `recipe`. */
|
|
10
|
+
export declare const templateSeedClaimSchema: Schema.Struct<{
|
|
11
|
+
readonly appId: Schema.refine<import("@fungi.computer/puffball").AppId, Schema.String>;
|
|
12
|
+
readonly sourceRevision: Schema.refine<import("@fungi.computer/puffball").SourceRevision, Schema.String>;
|
|
13
|
+
readonly sourceTreeDigest: Schema.refine<import("@fungi.computer/puffball").Sha256Digest, Schema.String>;
|
|
14
|
+
readonly seedRevision: Schema.refine<import("@fungi.computer/puffball").SourceRevision, Schema.String>;
|
|
15
|
+
readonly recipe: Schema.Struct<{
|
|
16
|
+
readonly kind: Schema.optional<Schema.Literals<readonly ["bundled-html", "source-html"]>>;
|
|
17
|
+
readonly bundle: Schema.Literal<true>;
|
|
18
|
+
readonly entry: Schema.String;
|
|
19
|
+
readonly format: Schema.Literals<readonly ["html", "esm", "assets"]>;
|
|
20
|
+
readonly assets: Schema.optional<Schema.Struct<{
|
|
21
|
+
readonly maxManifestBytes: Schema.Number;
|
|
22
|
+
readonly maxObjects: Schema.Number;
|
|
23
|
+
readonly maxObjectBytes: Schema.Number;
|
|
24
|
+
readonly maxTotalBytes: Schema.Number;
|
|
25
|
+
}>>;
|
|
26
|
+
readonly assetBase: Schema.optionalKey<Schema.String>;
|
|
27
|
+
readonly toolchain: Schema.$Record<Schema.String, Schema.String>;
|
|
28
|
+
readonly maxOutputBytes: Schema.Number;
|
|
29
|
+
readonly platform: Schema.Literals<readonly ["browser", "neutral"]>;
|
|
30
|
+
readonly backend: Schema.Union<readonly [Schema.Null, Schema.Struct<{
|
|
31
|
+
readonly entry: Schema.optional<Schema.String>;
|
|
32
|
+
readonly runtime: Schema.Struct<{
|
|
33
|
+
readonly kind: Schema.Literal<"durable-facet">;
|
|
34
|
+
readonly stateSchema: Schema.Number;
|
|
35
|
+
}>;
|
|
36
|
+
readonly compatibilityDate: Schema.String;
|
|
37
|
+
readonly exportName: Schema.String;
|
|
38
|
+
}>]>;
|
|
39
|
+
readonly target: Schema.Tuple<readonly [Schema.Literal<"es2022">]>;
|
|
40
|
+
}>;
|
|
41
|
+
}>;
|
|
42
|
+
export type TemplateSeedClaim = Schema.Schema.Type<typeof templateSeedClaimSchema>;
|
|
43
|
+
/** Decode one claim body at its I/O boundary, strictly; null when it is not
|
|
44
|
+
* exactly a seed claim. */
|
|
45
|
+
export declare function parseTemplateSeedClaim(body: string): TemplateSeedClaim | null;
|
|
46
|
+
/**
|
|
47
|
+
* Record the seed receipt for a template the operator already pushed. The
|
|
48
|
+
* template's current `main` must be exactly the claimed seed commit, whose
|
|
49
|
+
* hash pins its tree and history; the recipe digest is computed here; an
|
|
50
|
+
* existing receipt for that commit must carry the same provenance.
|
|
51
|
+
*/
|
|
52
|
+
export declare function recordTemplateSeed(receipts: PublisherTemplateSeedReceipts, artifacts: Pick<Artifacts, "readDefaultBranchHead">, rawAppId: string, claim: TemplateSeedClaim): Promise<{
|
|
53
|
+
readonly kind: "conflict";
|
|
54
|
+
readonly receipt?: undefined;
|
|
55
|
+
} | {
|
|
56
|
+
readonly kind: "recorded" | "existing";
|
|
57
|
+
readonly receipt: {
|
|
58
|
+
readonly appId: import("@fungi.computer/puffball").AppId;
|
|
59
|
+
readonly repository: string;
|
|
60
|
+
readonly sourceRevision: import("@fungi.computer/puffball").SourceRevision;
|
|
61
|
+
readonly sourceTreeDigest: import("@fungi.computer/puffball").Sha256Digest;
|
|
62
|
+
readonly seedRevision: import("@fungi.computer/puffball").SourceRevision;
|
|
63
|
+
readonly buildRecipeDigest: import("@fungi.computer/puffball").Sha256Digest;
|
|
64
|
+
};
|
|
65
|
+
} | {
|
|
66
|
+
readonly kind: "unavailable";
|
|
67
|
+
readonly receipt?: undefined;
|
|
68
|
+
}>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as Option from "effect/Option";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import { parseAppId } from "@fungi.computer/puffball";
|
|
4
|
+
import { appIdSchema, sha256DigestSchema, sourceRevisionSchema, } from "@fungi.computer/puffball/effect";
|
|
5
|
+
import { capturedPinnedRecipeSchema, digestPinnedRecipe, } from "../build-contract.js";
|
|
6
|
+
import { templateRepositoryForApp } from "./template-repository.js";
|
|
7
|
+
/** A seed claim is a few fields and one pinned recipe; the source itself
|
|
8
|
+
* arrived by `git push`. */
|
|
9
|
+
export const TEMPLATE_SEED_CLAIM_MAX_BYTES = 64 * 1024;
|
|
10
|
+
/** The operator's claim after pushing a projected source commit to the App's
|
|
11
|
+
* template: that `seedRevision` is that commit, projected from
|
|
12
|
+
* `sourceRevision`, and builds with `recipe`. */
|
|
13
|
+
export const templateSeedClaimSchema = Schema.Struct({
|
|
14
|
+
appId: appIdSchema,
|
|
15
|
+
sourceRevision: sourceRevisionSchema,
|
|
16
|
+
sourceTreeDigest: sha256DigestSchema,
|
|
17
|
+
seedRevision: sourceRevisionSchema,
|
|
18
|
+
recipe: capturedPinnedRecipeSchema,
|
|
19
|
+
});
|
|
20
|
+
/** Decode one claim body at its I/O boundary, strictly; null when it is not
|
|
21
|
+
* exactly a seed claim. */
|
|
22
|
+
export function parseTemplateSeedClaim(body) {
|
|
23
|
+
const decoded = Schema.decodeUnknownOption(Schema.fromJsonString(templateSeedClaimSchema), { onExcessProperty: "error" })(body);
|
|
24
|
+
return Option.isSome(decoded) ? decoded.value : null;
|
|
25
|
+
}
|
|
26
|
+
function logEvent(operation, cause) {
|
|
27
|
+
console.error(JSON.stringify({
|
|
28
|
+
source: "app-source",
|
|
29
|
+
operation,
|
|
30
|
+
failureKind: cause instanceof Error ? "Error" : "NonErrorThrown",
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Record the seed receipt for a template the operator already pushed. The
|
|
35
|
+
* template's current `main` must be exactly the claimed seed commit, whose
|
|
36
|
+
* hash pins its tree and history; the recipe digest is computed here; an
|
|
37
|
+
* existing receipt for that commit must carry the same provenance.
|
|
38
|
+
*/
|
|
39
|
+
export async function recordTemplateSeed(receipts, artifacts, rawAppId, claim) {
|
|
40
|
+
try {
|
|
41
|
+
const appId = parseAppId(rawAppId);
|
|
42
|
+
if (claim.appId !== appId)
|
|
43
|
+
return { kind: "conflict" };
|
|
44
|
+
const repository = templateRepositoryForApp(appId);
|
|
45
|
+
const head = await artifacts.readDefaultBranchHead(repository);
|
|
46
|
+
if (head === null || `git:${head}` !== claim.seedRevision)
|
|
47
|
+
return { kind: "conflict" };
|
|
48
|
+
const receipt = {
|
|
49
|
+
appId,
|
|
50
|
+
sourceRevision: claim.sourceRevision,
|
|
51
|
+
sourceTreeDigest: claim.sourceTreeDigest,
|
|
52
|
+
seedRevision: claim.seedRevision,
|
|
53
|
+
recipe: claim.recipe,
|
|
54
|
+
buildRecipeDigest: digestPinnedRecipe(claim.recipe),
|
|
55
|
+
};
|
|
56
|
+
const existing = await receipts.read(appId, claim.seedRevision);
|
|
57
|
+
if (existing !== null &&
|
|
58
|
+
(existing.sourceRevision !== receipt.sourceRevision ||
|
|
59
|
+
existing.sourceTreeDigest !== receipt.sourceTreeDigest ||
|
|
60
|
+
existing.buildRecipeDigest !== receipt.buildRecipeDigest))
|
|
61
|
+
return { kind: "conflict" };
|
|
62
|
+
// The write-once, digest-checked receipt owner records it.
|
|
63
|
+
await receipts.record(receipt);
|
|
64
|
+
return {
|
|
65
|
+
kind: existing === null ? "recorded" : "existing",
|
|
66
|
+
receipt: {
|
|
67
|
+
appId,
|
|
68
|
+
repository,
|
|
69
|
+
sourceRevision: receipt.sourceRevision,
|
|
70
|
+
sourceTreeDigest: receipt.sourceTreeDigest,
|
|
71
|
+
seedRevision: receipt.seedRevision,
|
|
72
|
+
buildRecipeDigest: receipt.buildRecipeDigest,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (cause) {
|
|
77
|
+
logEvent("operator.template.record", cause);
|
|
78
|
+
return { kind: "unavailable" };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TenantScope } from "../tenant.js";
|
|
2
|
+
import type { StandaloneAgentsEnv } from "./agents.js";
|
|
3
|
+
/** Host-only lookup from the already selected frame tenant; never derive it from an App or caller. */
|
|
4
|
+
export declare function agentDirectoryForScope(env: Pick<StandaloneAgentsEnv, "AGENT_DIRECTORY">, scope: Pick<TenantScope, "durableName">): DurableObjectStub<import("./agents.js").AgentDirectory>;
|