@fungi.computer/puffball 0.0.0-stage → 0.0.1
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 +443 -2
- package/dist/app-api.d.ts +7 -0
- package/dist/app-api.js +61 -0
- package/dist/app-backend-binding-vocabulary.d.ts +299 -0
- package/dist/app-backend-binding-vocabulary.js +257 -0
- package/dist/app-backend-bindings.d.ts +21 -0
- package/dist/app-backend-bindings.js +47 -0
- package/dist/app-declaration.d.ts +46 -0
- package/dist/app-declaration.js +115 -0
- package/dist/app-event-facts.d.ts +40 -0
- package/dist/app-event-facts.js +28 -0
- package/dist/app-method-context.d.ts +109 -0
- package/dist/app-method-context.js +9 -0
- package/dist/app-method-runtime.d.ts +86 -0
- package/dist/app-method-runtime.js +122 -0
- package/dist/app-origin-paths.d.ts +6 -0
- package/dist/app-origin-paths.js +6 -0
- package/dist/app-vocabulary.d.ts +189 -0
- package/dist/app-vocabulary.js +103 -0
- package/dist/app.d.ts +379 -0
- package/dist/app.js +71 -0
- package/dist/artifact-dispositions.d.ts +115 -0
- package/dist/artifact-dispositions.js +234 -0
- package/dist/assets.d.ts +54 -0
- package/dist/assets.js +89 -0
- package/dist/backend.d.ts +41 -0
- package/dist/backend.js +29 -0
- package/dist/background-replay.d.ts +115 -0
- package/dist/background-replay.js +175 -0
- package/dist/buyer-references.d.ts +69 -0
- package/dist/buyer-references.js +154 -0
- package/dist/cloudflare-assets.d.ts +48 -0
- package/dist/cloudflare-assets.js +105 -0
- package/dist/cloudflare.d.ts +254 -0
- package/dist/cloudflare.js +1619 -0
- package/dist/effect.d.ts +73 -0
- package/dist/effect.js +325 -0
- package/dist/git-representation.d.ts +13 -0
- package/dist/git-representation.js +16 -0
- package/dist/inbox.d.ts +132 -0
- package/dist/inbox.js +81 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +85 -0
- package/dist/internal/app-description.d.ts +14 -0
- package/dist/internal/app-description.js +98 -0
- package/dist/internal/app-release-artifact-reader.d.ts +26 -0
- package/dist/internal/app-release-artifact-reader.js +1 -0
- package/dist/internal/app-release-contract.d.ts +540 -0
- package/dist/internal/app-release-contract.js +169 -0
- package/dist/internal/app-release-schema.d.ts +1676 -0
- package/dist/internal/app-release-schema.js +386 -0
- package/dist/internal/app-release-store.d.ts +47 -0
- package/dist/internal/app-release-store.js +1 -0
- package/dist/internal/background-sync.d.ts +7 -0
- package/dist/internal/background-sync.js +10 -0
- package/dist/internal/deterministic-memory-app-release-store.d.ts +14 -0
- package/dist/internal/deterministic-memory-app-release-store.js +319 -0
- package/dist/internal/failure.d.ts +2 -0
- package/dist/internal/failure.js +9 -0
- package/dist/internal/immutable-release-material.d.ts +3 -0
- package/dist/internal/immutable-release-material.js +13 -0
- package/dist/internal/promise-app-release-store-adapter.d.ts +9 -0
- package/dist/internal/promise-app-release-store-adapter.js +65 -0
- package/dist/internal/promise-artifact-reader-adapter.d.ts +9 -0
- package/dist/internal/promise-artifact-reader-adapter.js +18 -0
- package/dist/internal/public-route-sql.d.ts +10 -0
- package/dist/internal/public-route-sql.js +239 -0
- package/dist/internal/public-website-authority.d.ts +338 -0
- package/dist/internal/public-website-authority.js +50 -0
- package/dist/internal/public-website-delivery.d.ts +59 -0
- package/dist/internal/public-website-delivery.js +232 -0
- package/dist/internal/public-website-fetch.d.ts +8 -0
- package/dist/internal/public-website-fetch.js +54 -0
- package/dist/internal/release-transition-material.d.ts +3 -0
- package/dist/internal/release-transition-material.js +11 -0
- package/dist/internal/verified-artifact-snapshot.d.ts +41 -0
- package/dist/internal/verified-artifact-snapshot.js +91 -0
- package/dist/public-route-moderation.d.ts +83 -0
- package/dist/public-route-moderation.js +236 -0
- package/dist/public-route-policy.d.ts +51 -0
- package/dist/public-route-policy.js +88 -0
- package/dist/public-routes.d.ts +34 -0
- package/dist/public-routes.js +136 -0
- package/dist/publisher.d.ts +61 -0
- package/dist/publisher.js +16 -0
- package/dist/safety-takedown.d.ts +56 -0
- package/dist/safety-takedown.js +146 -0
- package/dist/seller-closure-notice.d.ts +14 -0
- package/dist/seller-closure-notice.js +14 -0
- package/dist/seller-closure.d.ts +15 -0
- package/dist/seller-closure.js +31 -0
- package/dist/testing.d.ts +8 -0
- package/dist/testing.js +47 -0
- package/docs/api/README.md +26 -0
- package/docs/api/app-backend-binding-vocabulary.md +276 -0
- package/docs/api/app-backend-bindings.md +141 -0
- package/docs/api/app-event-facts.md +110 -0
- package/docs/api/app-method-context.md +166 -0
- package/docs/api/app-method-runtime.md +330 -0
- package/docs/api/backend.md +108 -0
- package/docs/api/background-replay.md +516 -0
- package/docs/api/cloudflare.md +1044 -0
- package/docs/api/effect.md +2429 -0
- package/docs/api/git-representation.md +56 -0
- package/docs/api/inbox.md +289 -0
- package/docs/api/index.md +2868 -0
- package/docs/api/publisher.md +147 -0
- package/docs/api/testing.md +31 -0
- package/package.json +158 -3
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Result from "effect/Result";
|
|
3
|
+
import type { PublicSurface, PublicSurfaceAuthority, PublicWebsiteRepresentation, Release, Sha256Digest, StableRoute } from "./app-release-contract.js";
|
|
4
|
+
import { AppReleasePublicWebsiteNotFoundError, AppReleasePublicWebsiteUnavailableError } from "./app-release-contract.js";
|
|
5
|
+
import type { AppReleaseArtifactReader } from "./app-release-artifact-reader.js";
|
|
6
|
+
import type { AppReleaseStore } from "./app-release-store.js";
|
|
7
|
+
import { type ArtifactSnapshotPolicy } from "./verified-artifact-snapshot.js";
|
|
8
|
+
export type PublicWebsiteFailure = AppReleasePublicWebsiteNotFoundError | AppReleasePublicWebsiteUnavailableError;
|
|
9
|
+
export type EligibleSurfaceRelease = Readonly<{
|
|
10
|
+
appId: Release["appId"];
|
|
11
|
+
releaseId: Release["releaseId"];
|
|
12
|
+
stableRoute: StableRoute;
|
|
13
|
+
artifactDigest: Sha256Digest;
|
|
14
|
+
mediaType: "application/vnd.fungi.app+json";
|
|
15
|
+
}>;
|
|
16
|
+
/** Resolve the selected Surface's persisted metadata without loading bytes. */
|
|
17
|
+
export declare function createPublicSurfaceAuthorityResolver(options: {
|
|
18
|
+
readonly store: AppReleaseStore;
|
|
19
|
+
}): (stableRoute: StableRoute) => Effect.Effect<PublicSurfaceAuthority, PublicWebsiteFailure>;
|
|
20
|
+
/** Resolve one previously selected Release's current metadata authority. */
|
|
21
|
+
export declare function createPublicSurfaceAuthorityVersionResolver(options: {
|
|
22
|
+
readonly store: AppReleaseStore;
|
|
23
|
+
}): (stableRoute: StableRoute, releaseId: Release["releaseId"]) => Effect.Effect<PublicSurfaceAuthority, PublicWebsiteFailure>;
|
|
24
|
+
/** Resolve the immutable owner of one stable route without selecting a Release. */
|
|
25
|
+
export declare function createPublicSurfaceResolver(options: {
|
|
26
|
+
readonly store: AppReleaseStore;
|
|
27
|
+
}): (stableRoute: StableRoute) => Effect.Effect<PublicSurface, PublicWebsiteFailure>;
|
|
28
|
+
/** Read current eligible Surface authority without loading executable bytes. */
|
|
29
|
+
export declare function createEligibleSurfaceReleaseResolver(options: {
|
|
30
|
+
readonly store: AppReleaseStore;
|
|
31
|
+
}): (stableRoute: StableRoute) => Effect.Effect<EligibleSurfaceRelease, PublicWebsiteFailure>;
|
|
32
|
+
/** Read one atomic directory; eligibility failures belong to individual entries. */
|
|
33
|
+
export declare function createEligibleSurfaceReleasesResolver(options: {
|
|
34
|
+
readonly store: AppReleaseStore;
|
|
35
|
+
}): (routes: readonly StableRoute[]) => Effect.Effect<readonly Result.Result<EligibleSurfaceRelease, PublicWebsiteFailure>[], PublicWebsiteFailure>;
|
|
36
|
+
/** Resolve one eligible Release that has previously been selected on this Surface. */
|
|
37
|
+
export declare function createEligibleSurfaceVersionResolver(options: {
|
|
38
|
+
readonly store: AppReleaseStore;
|
|
39
|
+
}): (stableRoute: StableRoute, releaseId: Release["releaseId"]) => Effect.Effect<EligibleSurfaceRelease, PublicWebsiteFailure>;
|
|
40
|
+
/** Media types accepted by the public website representation resolvers. */
|
|
41
|
+
export type PublicWebsiteMediaType = "text/html; charset=utf-8" | "application/vnd.fungi.app-assets+json";
|
|
42
|
+
/** Compose the one deep public delivery operation and its private cache. */
|
|
43
|
+
export declare function createPublicWebsiteResolver<MediaType extends PublicWebsiteMediaType>(options: {
|
|
44
|
+
readonly mediaType: MediaType;
|
|
45
|
+
readonly store: AppReleaseStore;
|
|
46
|
+
readonly artifactReader: AppReleaseArtifactReader;
|
|
47
|
+
readonly artifactPolicy?: ArtifactSnapshotPolicy;
|
|
48
|
+
}): (stableRoute: StableRoute) => Effect.Effect<Omit<PublicWebsiteRepresentation, "mediaType"> & {
|
|
49
|
+
readonly mediaType: MediaType;
|
|
50
|
+
}, PublicWebsiteFailure>;
|
|
51
|
+
/** Compose pinned public delivery for a Release previously selected by a Surface. */
|
|
52
|
+
export declare function createPublicWebsiteVersionResolver<MediaType extends PublicWebsiteMediaType>(options: {
|
|
53
|
+
readonly mediaType: MediaType;
|
|
54
|
+
readonly store: AppReleaseStore;
|
|
55
|
+
readonly artifactReader: AppReleaseArtifactReader;
|
|
56
|
+
readonly artifactPolicy?: ArtifactSnapshotPolicy;
|
|
57
|
+
}): (stableRoute: StableRoute, releaseId: Release["releaseId"]) => Effect.Effect<Omit<PublicWebsiteRepresentation, "mediaType"> & {
|
|
58
|
+
readonly mediaType: MediaType;
|
|
59
|
+
}, PublicWebsiteFailure>;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Result from "effect/Result";
|
|
3
|
+
import { AppReleasePublicWebsiteNotFoundError, AppReleasePublicWebsiteUnavailableError, } from "./app-release-contract.js";
|
|
4
|
+
import { decodeStableRoute } from "./app-release-schema.js";
|
|
5
|
+
import { getEffectPublicWebsiteAuthorityReader, } from "./public-website-authority.js";
|
|
6
|
+
import { sameImmutableReleaseMaterial } from "./immutable-release-material.js";
|
|
7
|
+
import { readVerifiedArtifactSnapshot, DEFAULT_ARTIFACT_SNAPSHOT_POLICY, } from "./verified-artifact-snapshot.js";
|
|
8
|
+
/** Resolve the selected Surface's persisted metadata without loading bytes. */
|
|
9
|
+
export function createPublicSurfaceAuthorityResolver(options) {
|
|
10
|
+
return (stableRoute) => decodeRoute(stableRoute).pipe(Effect.flatMap((route) => readAuthority(options.store, route).pipe(Effect.flatMap((authority) => resolveSurfaceAuthority(authority, route)))));
|
|
11
|
+
}
|
|
12
|
+
/** Resolve one previously selected Release's current metadata authority. */
|
|
13
|
+
export function createPublicSurfaceAuthorityVersionResolver(options) {
|
|
14
|
+
return (stableRoute, releaseId) => decodeRoute(stableRoute).pipe(Effect.flatMap((route) => readAuthority(options.store, route, releaseId).pipe(Effect.flatMap((authority) => resolveSurfaceAuthority(authority, route)))));
|
|
15
|
+
}
|
|
16
|
+
/** Resolve the immutable owner of one stable route without selecting a Release. */
|
|
17
|
+
export function createPublicSurfaceResolver(options) {
|
|
18
|
+
return (stableRoute) => decodeRoute(stableRoute).pipe(Effect.flatMap((route) => readAuthority(options.store, route).pipe(Effect.flatMap((authority) => {
|
|
19
|
+
if (authority.kind === "absent")
|
|
20
|
+
return Effect.fail(notFound());
|
|
21
|
+
if (authority.kind === "ambiguous" ||
|
|
22
|
+
authority.surface.stableRoute !== route)
|
|
23
|
+
return Effect.fail(unavailable());
|
|
24
|
+
return Effect.succeed(authority.surface);
|
|
25
|
+
}))));
|
|
26
|
+
}
|
|
27
|
+
/** Read current eligible Surface authority without loading executable bytes. */
|
|
28
|
+
export function createEligibleSurfaceReleaseResolver(options) {
|
|
29
|
+
const resolve = createEligibleSurfaceReleasesResolver(options);
|
|
30
|
+
return (route) => resolve([route]).pipe(Effect.flatMap((selections) => {
|
|
31
|
+
const selection = selections[0];
|
|
32
|
+
return selection === undefined
|
|
33
|
+
? Effect.fail(unavailable())
|
|
34
|
+
: Effect.fromResult(selection);
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
/** Read one atomic directory; eligibility failures belong to individual entries. */
|
|
38
|
+
export function createEligibleSurfaceReleasesResolver(options) {
|
|
39
|
+
return (routes) => Effect.gen(function* () {
|
|
40
|
+
if (routes.length > 100)
|
|
41
|
+
return yield* Effect.fail(unavailable());
|
|
42
|
+
const parsed = yield* Effect.forEach(routes, decodeRoute);
|
|
43
|
+
if (parsed.length === 0)
|
|
44
|
+
return [];
|
|
45
|
+
const read = getEffectPublicWebsiteAuthorityReader(options.store);
|
|
46
|
+
if (read === undefined)
|
|
47
|
+
return yield* Effect.fail(unavailable());
|
|
48
|
+
const authorities = yield* read(parsed.map((stableRoute) => ({ stableRoute }))).pipe(Effect.mapError(unavailable));
|
|
49
|
+
if (authorities.length !== parsed.length)
|
|
50
|
+
return yield* Effect.fail(unavailable());
|
|
51
|
+
return yield* Effect.forEach(parsed, (route, index) => {
|
|
52
|
+
const authority = authorities[index];
|
|
53
|
+
if (authority === undefined)
|
|
54
|
+
return Effect.fail(unavailable());
|
|
55
|
+
return resolveEligibleAuthority(authority, route).pipe(Effect.map(({ release }) => ({
|
|
56
|
+
appId: release.appId,
|
|
57
|
+
releaseId: release.releaseId,
|
|
58
|
+
stableRoute: route,
|
|
59
|
+
artifactDigest: release.artifact.artifactDigest,
|
|
60
|
+
mediaType: "application/vnd.fungi.app+json",
|
|
61
|
+
})), Effect.result);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Resolve one eligible Release that has previously been selected on this Surface. */
|
|
66
|
+
export function createEligibleSurfaceVersionResolver(options) {
|
|
67
|
+
return (stableRoute, releaseId) => createPublicSurfaceAuthorityVersionResolver(options)(stableRoute, releaseId).pipe(Effect.flatMap((authority) => authority.revocation.kind === "active"
|
|
68
|
+
? Effect.succeed({
|
|
69
|
+
appId: authority.release.appId,
|
|
70
|
+
releaseId: authority.release.releaseId,
|
|
71
|
+
stableRoute: authority.surface.stableRoute,
|
|
72
|
+
artifactDigest: authority.digest,
|
|
73
|
+
mediaType: "application/vnd.fungi.app+json",
|
|
74
|
+
})
|
|
75
|
+
: Effect.fail(unavailable())));
|
|
76
|
+
}
|
|
77
|
+
/** Compose the one deep public delivery operation and its private cache. */
|
|
78
|
+
export function createPublicWebsiteResolver(options) {
|
|
79
|
+
let cache;
|
|
80
|
+
const artifactPolicy = options.artifactPolicy ?? DEFAULT_ARTIFACT_SNAPSHOT_POLICY;
|
|
81
|
+
return (stableRoute) => decodeRoute(stableRoute).pipe(Effect.flatMap((route) => readAuthority(options.store, route).pipe(Effect.map((authority) => ({ authority, route })))), Effect.flatMap(({ authority, route }) => resolveEligibleAuthority(authority, route).pipe(Effect.flatMap((eligible) => readVerifiedArtifact(options.artifactReader, eligible.ui, artifactPolicy, options.mediaType).pipe(Effect.flatMap((artifact) => readAuthority(options.store, route).pipe(Effect.flatMap((second) => sameAuthority(authority, second)
|
|
82
|
+
? Effect.succeed(representation(eligible.release.releaseId, selectCache(cache, eligible.ui.digest, artifact))).pipe(Effect.tap((result) => {
|
|
83
|
+
cache = {
|
|
84
|
+
digest: result.digest,
|
|
85
|
+
artifact: {
|
|
86
|
+
digest: result.digest,
|
|
87
|
+
mediaType: result.mediaType,
|
|
88
|
+
byteLength: result.byteLength,
|
|
89
|
+
bytes: result.bytes,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
return Effect.void;
|
|
93
|
+
}))
|
|
94
|
+
: Effect.fail(unavailable())))))))));
|
|
95
|
+
}
|
|
96
|
+
/** Compose pinned public delivery for a Release previously selected by a Surface. */
|
|
97
|
+
export function createPublicWebsiteVersionResolver(options) {
|
|
98
|
+
let cache;
|
|
99
|
+
const artifactPolicy = options.artifactPolicy ?? DEFAULT_ARTIFACT_SNAPSHOT_POLICY;
|
|
100
|
+
return (stableRoute, releaseId) => decodeRoute(stableRoute).pipe(Effect.flatMap((route) => readAuthority(options.store, route, releaseId).pipe(Effect.map((authority) => ({ authority, route })))), Effect.flatMap(({ authority, route }) => resolveEligibleAuthority(authority, route).pipe(Effect.flatMap((eligible) => readVerifiedArtifact(options.artifactReader, eligible.ui, artifactPolicy, options.mediaType).pipe(Effect.flatMap((artifact) => readAuthority(options.store, route, releaseId).pipe(Effect.flatMap((second) => {
|
|
101
|
+
if (!sameAuthority(authority, second))
|
|
102
|
+
return Effect.fail(unavailable());
|
|
103
|
+
const selected = selectCache(cache, eligible.ui.digest, artifact);
|
|
104
|
+
cache = { digest: selected.digest, artifact: selected };
|
|
105
|
+
return Effect.succeed(representation(eligible.release.releaseId, selected));
|
|
106
|
+
}))))))));
|
|
107
|
+
}
|
|
108
|
+
function decodeRoute(input) {
|
|
109
|
+
return Result.match(decodeStableRoute(input), {
|
|
110
|
+
onFailure: () => Effect.fail(notFound()),
|
|
111
|
+
onSuccess: Effect.succeed,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function readAuthority(store, stableRoute, expectedReleaseId) {
|
|
115
|
+
const read = getEffectPublicWebsiteAuthorityReader(store);
|
|
116
|
+
const request = expectedReleaseId === undefined
|
|
117
|
+
? { stableRoute }
|
|
118
|
+
: { stableRoute, expectedReleaseId };
|
|
119
|
+
return read === undefined
|
|
120
|
+
? Effect.fail(unavailable())
|
|
121
|
+
: read([request]).pipe(Effect.mapError(unavailable), Effect.flatMap((values) => values[0] === undefined
|
|
122
|
+
? Effect.fail(unavailable())
|
|
123
|
+
: Effect.succeed(values[0])));
|
|
124
|
+
}
|
|
125
|
+
function resolveEligibleAuthority(authority, requestedRoute) {
|
|
126
|
+
return resolveSurfaceAuthority(authority, requestedRoute).pipe(Effect.flatMap((resolved) => resolved.revocation.kind === "active"
|
|
127
|
+
? resolvePublishedUi(resolved)
|
|
128
|
+
: Effect.fail(unavailable())));
|
|
129
|
+
}
|
|
130
|
+
function resolveSurfaceAuthority(authority, requestedRoute) {
|
|
131
|
+
if (authority.kind === "absent")
|
|
132
|
+
return Effect.fail(notFound());
|
|
133
|
+
if (authority.kind === "ambiguous")
|
|
134
|
+
return Effect.fail(unavailable());
|
|
135
|
+
if (authority.kind === "unselected")
|
|
136
|
+
return Effect.fail(notFound());
|
|
137
|
+
if (authority.surface.stableRoute !== requestedRoute)
|
|
138
|
+
return Effect.fail(unavailable());
|
|
139
|
+
if (authority.release.kind !== "found")
|
|
140
|
+
return Effect.fail(unavailable());
|
|
141
|
+
const release = authority.release.release;
|
|
142
|
+
const verification = authority.release.verification;
|
|
143
|
+
if (verification.kind !== "verified")
|
|
144
|
+
return Effect.fail(unavailable());
|
|
145
|
+
const coherent = [
|
|
146
|
+
authority.releaseId === release.releaseId,
|
|
147
|
+
authority.app.appId === authority.surface.appId,
|
|
148
|
+
release.appId === authority.surface.appId,
|
|
149
|
+
verification.digest === release.artifact.artifactDigest,
|
|
150
|
+
].every(Boolean);
|
|
151
|
+
if (!coherent)
|
|
152
|
+
return Effect.fail(unavailable());
|
|
153
|
+
return Effect.succeed({
|
|
154
|
+
app: authority.app,
|
|
155
|
+
sequence: authority.sequence,
|
|
156
|
+
currentReleaseId: authority.currentReleaseId,
|
|
157
|
+
release,
|
|
158
|
+
surface: authority.surface,
|
|
159
|
+
digest: verification.digest,
|
|
160
|
+
revocation: authority.release.revocation,
|
|
161
|
+
description: verification.description,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function resolvePublishedUi(authority) {
|
|
165
|
+
if (authority.description.ui === null)
|
|
166
|
+
return Effect.fail(unavailable());
|
|
167
|
+
return Effect.succeed({
|
|
168
|
+
release: authority.release,
|
|
169
|
+
ui: authority.description.ui,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function readVerifiedArtifact(artifactReader, artifactReference, artifactPolicy, mediaType) {
|
|
173
|
+
return readVerifiedArtifactSnapshot(artifactReader, {
|
|
174
|
+
artifactRef: artifactReference.artifactRef,
|
|
175
|
+
expectedDigest: artifactReference.digest,
|
|
176
|
+
expectedObject: {
|
|
177
|
+
version: artifactReference.version,
|
|
178
|
+
byteLength: artifactReference.byteLength,
|
|
179
|
+
},
|
|
180
|
+
}, artifactPolicy).pipe(Effect.mapError(unavailable), Effect.flatMap((artifact) => artifact.mediaType === mediaType
|
|
181
|
+
? Effect.succeed({
|
|
182
|
+
...artifact,
|
|
183
|
+
mediaType,
|
|
184
|
+
})
|
|
185
|
+
: Effect.fail(unavailable())));
|
|
186
|
+
}
|
|
187
|
+
function selectCache(cache, expectedDigest, artifact) {
|
|
188
|
+
return cache?.digest === expectedDigest && artifact.digest === expectedDigest
|
|
189
|
+
? cache.artifact
|
|
190
|
+
: artifact;
|
|
191
|
+
}
|
|
192
|
+
function representation(releaseId, artifact) {
|
|
193
|
+
return {
|
|
194
|
+
releaseId,
|
|
195
|
+
digest: artifact.digest,
|
|
196
|
+
mediaType: artifact.mediaType,
|
|
197
|
+
byteLength: artifact.byteLength,
|
|
198
|
+
bytes: {
|
|
199
|
+
byteLength: artifact.byteLength,
|
|
200
|
+
copy: () => artifact.bytes.copy(),
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function sameAuthority(left, right) {
|
|
205
|
+
if (left.kind !== "selected" || right.kind !== "selected")
|
|
206
|
+
return false;
|
|
207
|
+
if (left.surface.publicSurfaceId !== right.surface.publicSurfaceId ||
|
|
208
|
+
left.surface.appId !== right.surface.appId ||
|
|
209
|
+
left.surface.stableRoute !== right.surface.stableRoute) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
return (left.releaseId === right.releaseId &&
|
|
213
|
+
sameLifecycle(left.release, right.release));
|
|
214
|
+
}
|
|
215
|
+
function sameLifecycle(left, right) {
|
|
216
|
+
if (left.kind !== "found" || right.kind !== "found")
|
|
217
|
+
return left.kind === right.kind;
|
|
218
|
+
const leftRelease = left.release;
|
|
219
|
+
const rightRelease = right.release;
|
|
220
|
+
return (sameImmutableReleaseMaterial(leftRelease, rightRelease) &&
|
|
221
|
+
left.verification.kind === right.verification.kind &&
|
|
222
|
+
(left.verification.kind !== "verified" ||
|
|
223
|
+
right.verification.kind !== "verified" ||
|
|
224
|
+
left.verification.digest === right.verification.digest) &&
|
|
225
|
+
left.revocation.kind === right.revocation.kind);
|
|
226
|
+
}
|
|
227
|
+
function notFound() {
|
|
228
|
+
return new AppReleasePublicWebsiteNotFoundError();
|
|
229
|
+
}
|
|
230
|
+
function unavailable() {
|
|
231
|
+
return new AppReleasePublicWebsiteUnavailableError();
|
|
232
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppReleaseCatalog } from "./app-release-contract.js";
|
|
2
|
+
export type AppReleasePublicWebsiteFetchHandler = Readonly<{
|
|
3
|
+
fetch(request: Request): Promise<Response>;
|
|
4
|
+
}>;
|
|
5
|
+
/** Private Fetch projection for the anonymous public-website tracer. */
|
|
6
|
+
export declare function createPublicWebsiteFetchHandler(options: {
|
|
7
|
+
readonly resolvePublicWebsite: AppReleaseCatalog["resolvePublicWebsite"];
|
|
8
|
+
}): AppReleasePublicWebsiteFetchHandler;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AppReleasePublicWebsiteNotFoundError } from "./app-release-contract.js";
|
|
2
|
+
import { decodeStableRoute } from "./app-release-schema.js";
|
|
3
|
+
const htmlContentType = "text/html; charset=utf-8";
|
|
4
|
+
const notFoundHtml = "<!doctype html><title>Not found</title>";
|
|
5
|
+
const unavailableHtml = "<!doctype html><title>Unavailable</title>";
|
|
6
|
+
const methodNotAllowedHtml = "<!doctype html><title>Method not allowed</title>";
|
|
7
|
+
/** Private Fetch projection for the anonymous public-website tracer. */
|
|
8
|
+
export function createPublicWebsiteFetchHandler(options) {
|
|
9
|
+
return {
|
|
10
|
+
async fetch(request) {
|
|
11
|
+
if (request.method !== "GET")
|
|
12
|
+
return methodNotAllowed();
|
|
13
|
+
const stableRoute = parsePathname(request.url);
|
|
14
|
+
if (stableRoute === undefined)
|
|
15
|
+
return notFound();
|
|
16
|
+
try {
|
|
17
|
+
const representation = await options.resolvePublicWebsite(stableRoute);
|
|
18
|
+
return delivered(representation);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return error instanceof AppReleasePublicWebsiteNotFoundError
|
|
22
|
+
? notFound()
|
|
23
|
+
: unavailable();
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function parsePathname(requestUrl) {
|
|
29
|
+
if (!URL.canParse(requestUrl))
|
|
30
|
+
return undefined;
|
|
31
|
+
const decoded = decodeStableRoute(new URL(requestUrl).pathname);
|
|
32
|
+
return decoded._tag === "Success" ? decoded.success : undefined;
|
|
33
|
+
}
|
|
34
|
+
function delivered(representation) {
|
|
35
|
+
return new Response(new Uint8Array(representation.bytes.copy()), {
|
|
36
|
+
status: 200,
|
|
37
|
+
headers: { "content-type": htmlContentType },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function notFound() {
|
|
41
|
+
return fixedResponse(notFoundHtml, 404);
|
|
42
|
+
}
|
|
43
|
+
function unavailable() {
|
|
44
|
+
return fixedResponse(unavailableHtml, 503);
|
|
45
|
+
}
|
|
46
|
+
function methodNotAllowed() {
|
|
47
|
+
return fixedResponse(methodNotAllowedHtml, 405);
|
|
48
|
+
}
|
|
49
|
+
function fixedResponse(body, status) {
|
|
50
|
+
return new Response(body, {
|
|
51
|
+
status,
|
|
52
|
+
headers: { "content-type": htmlContentType },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Compare the durable material owned by one operation-scoped transition key. */
|
|
2
|
+
export function sameReleaseTransitionMaterial(left, right) {
|
|
3
|
+
if (left.kind !== right.kind ||
|
|
4
|
+
left.idempotencyKey !== right.idempotencyKey ||
|
|
5
|
+
left.releaseId !== right.releaseId) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
return (left.kind === "revocation" ||
|
|
9
|
+
(right.kind !== "revocation" &&
|
|
10
|
+
left.publicSurfaceId === right.publicSurfaceId));
|
|
11
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type { ArtifactRef, ArtifactVerificationIntent, Sha256Digest, VerifiedArtifactRead } from "./app-release-contract.js";
|
|
3
|
+
import type { AppReleaseArtifactReader } from "./app-release-artifact-reader.js";
|
|
4
|
+
/** Private, package-owned immutable artifact snapshot shape. */
|
|
5
|
+
export type VerifiedArtifactSnapshot = Omit<VerifiedArtifactRead, "kind">;
|
|
6
|
+
/** Trusted composition limit for reading one immutable artifact. */
|
|
7
|
+
export type ArtifactSnapshotPolicy = Readonly<{
|
|
8
|
+
/** Maximum verified artifact size in bytes. */
|
|
9
|
+
readonly maxBytes: number;
|
|
10
|
+
readonly maxManifestBytes?: number;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const DEFAULT_ARTIFACT_SNAPSHOT_POLICY: ArtifactSnapshotPolicy;
|
|
13
|
+
export declare function createArtifactSnapshotPolicy(maxBytes: number, maxManifestBytes?: number): ArtifactSnapshotPolicy;
|
|
14
|
+
/** Closed reasons that an immutable artifact snapshot cannot be established. */
|
|
15
|
+
export type VerifiedArtifactSnapshotFailure = {
|
|
16
|
+
readonly kind: "unavailable";
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: "missing";
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: "digest-mismatch";
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: "invalid-snapshot";
|
|
23
|
+
readonly cause?: unknown;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Read, decode, classify, and own one hostile artifact-reader result.
|
|
27
|
+
*
|
|
28
|
+
* Callers receive only a bounded immutable snapshot or this closed internal
|
|
29
|
+
* failure vocabulary. Publication may request complete asset verification;
|
|
30
|
+
* ordinary delivery leaves that work to the requested-object handler. Reader
|
|
31
|
+
* and schema details do not escape this module.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readVerifiedArtifactSnapshot(artifactReader: AppReleaseArtifactReader, input: {
|
|
34
|
+
readonly artifactRef: ArtifactRef;
|
|
35
|
+
readonly expectedDigest: Sha256Digest;
|
|
36
|
+
readonly expectedObject?: {
|
|
37
|
+
readonly version: string;
|
|
38
|
+
readonly byteLength: number;
|
|
39
|
+
};
|
|
40
|
+
readonly verification?: ArtifactVerificationIntent;
|
|
41
|
+
}, policy?: ArtifactSnapshotPolicy): Effect.Effect<VerifiedArtifactSnapshot, VerifiedArtifactSnapshotFailure>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { decodeArtifactReadResult } from "./app-release-schema.js";
|
|
3
|
+
import { reportFailure } from "./failure.js";
|
|
4
|
+
export const DEFAULT_ARTIFACT_SNAPSHOT_POLICY = Object.freeze({ maxBytes: 65_536 });
|
|
5
|
+
export function createArtifactSnapshotPolicy(maxBytes, maxManifestBytes) {
|
|
6
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1)
|
|
7
|
+
throw new Error("Invalid artifact snapshot policy");
|
|
8
|
+
if (maxManifestBytes !== undefined &&
|
|
9
|
+
(!Number.isSafeInteger(maxManifestBytes) || maxManifestBytes < 1))
|
|
10
|
+
throw new Error("Invalid asset manifest policy");
|
|
11
|
+
return Object.freeze(maxManifestBytes === undefined
|
|
12
|
+
? { maxBytes }
|
|
13
|
+
: { maxBytes, maxManifestBytes });
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Read, decode, classify, and own one hostile artifact-reader result.
|
|
17
|
+
*
|
|
18
|
+
* Callers receive only a bounded immutable snapshot or this closed internal
|
|
19
|
+
* failure vocabulary. Publication may request complete asset verification;
|
|
20
|
+
* ordinary delivery leaves that work to the requested-object handler. Reader
|
|
21
|
+
* and schema details do not escape this module.
|
|
22
|
+
*/
|
|
23
|
+
export function readVerifiedArtifactSnapshot(artifactReader, input, policy = DEFAULT_ARTIFACT_SNAPSHOT_POLICY) {
|
|
24
|
+
return artifactReader.read(input).pipe(Effect.mapError(() => artifactFailure("unavailable")), Effect.flatMap((raw) => decodeArtifactReadResult(raw).pipe(Effect.mapError(() => artifactFailure("invalid-snapshot")), Effect.flatMap((result) => {
|
|
25
|
+
switch (result.kind) {
|
|
26
|
+
case "unavailable":
|
|
27
|
+
return Effect.fail(artifactFailure("unavailable"));
|
|
28
|
+
case "missing":
|
|
29
|
+
return Effect.fail(artifactFailure("missing"));
|
|
30
|
+
case "digest-mismatch":
|
|
31
|
+
return Effect.fail(artifactFailure("digest-mismatch"));
|
|
32
|
+
case "verified": {
|
|
33
|
+
if (result.digest !== input.expectedDigest) {
|
|
34
|
+
return Effect.fail(artifactFailure("digest-mismatch"));
|
|
35
|
+
}
|
|
36
|
+
let snapshot;
|
|
37
|
+
try {
|
|
38
|
+
snapshot = createVerifiedArtifactSnapshot(result, policy);
|
|
39
|
+
}
|
|
40
|
+
catch (cause) {
|
|
41
|
+
reportFailure("copy verified artifact snapshot", cause);
|
|
42
|
+
return Effect.fail(artifactFailure("invalid-snapshot", cause));
|
|
43
|
+
}
|
|
44
|
+
return snapshot === undefined
|
|
45
|
+
? Effect.fail(artifactFailure("invalid-snapshot"))
|
|
46
|
+
: Effect.succeed(snapshot);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}))));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Copy hostile verified bytes into the package-owned immutable snapshot.
|
|
53
|
+
*
|
|
54
|
+
* Undefined means that the foreign result could not satisfy the fixed A1
|
|
55
|
+
* snapshot bounds. The returned closure only retains the owned copy.
|
|
56
|
+
*/
|
|
57
|
+
function createVerifiedArtifactSnapshot(input, policy) {
|
|
58
|
+
const isManifest = input.mediaType === "application/vnd.fungi.app-assets+json" ||
|
|
59
|
+
input.mediaType === "application/vnd.fungi.app+json";
|
|
60
|
+
const maxBytes = isManifest
|
|
61
|
+
? (policy.maxManifestBytes ?? policy.maxBytes)
|
|
62
|
+
: policy.maxBytes;
|
|
63
|
+
if (!Number.isInteger(input.byteLength) ||
|
|
64
|
+
input.byteLength < 1 ||
|
|
65
|
+
input.byteLength > maxBytes) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
const bytes = input.bytes;
|
|
69
|
+
if (bytes.byteLength !== input.byteLength) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const foreignCopy = bytes.copy();
|
|
73
|
+
if (!(foreignCopy instanceof Uint8Array) ||
|
|
74
|
+
foreignCopy.byteLength !== input.byteLength) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const ownedBytes = new Uint8Array(input.byteLength);
|
|
78
|
+
ownedBytes.set(foreignCopy);
|
|
79
|
+
return {
|
|
80
|
+
digest: input.digest,
|
|
81
|
+
mediaType: input.mediaType,
|
|
82
|
+
byteLength: ownedBytes.byteLength,
|
|
83
|
+
bytes: {
|
|
84
|
+
byteLength: ownedBytes.byteLength,
|
|
85
|
+
copy: () => ownedBytes.slice(),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function artifactFailure(kind, cause) {
|
|
90
|
+
return cause === undefined ? { kind } : { kind, cause };
|
|
91
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import type { AppReleaseD1Database } from "./cloudflare.js";
|
|
3
|
+
declare const commandSchema: Schema.Struct<{
|
|
4
|
+
readonly operationId: Schema.String;
|
|
5
|
+
readonly operatorId: Schema.String;
|
|
6
|
+
readonly reason: Schema.String;
|
|
7
|
+
readonly target: Schema.Struct<{
|
|
8
|
+
readonly publicSurfaceId: Schema.refine<import("./index.js").PublicSurfaceId, Schema.String>;
|
|
9
|
+
readonly appId: Schema.refine<import("./index.js").AppId, Schema.String>;
|
|
10
|
+
readonly teamId: Schema.String;
|
|
11
|
+
readonly stableRoute: Schema.refine<import("./index.js").StableRoute, Schema.String>;
|
|
12
|
+
}>;
|
|
13
|
+
}>;
|
|
14
|
+
export type PublicRouteRemoval = Schema.Schema.Type<typeof commandSchema>;
|
|
15
|
+
declare const operationSchema: Schema.Struct<{
|
|
16
|
+
readonly operation_id: Schema.String;
|
|
17
|
+
readonly kind: Schema.Literals<readonly ["unlist-and-release", "re-allow"]>;
|
|
18
|
+
readonly public_surface_id: Schema.refine<import("./index.js").PublicSurfaceId, Schema.String>;
|
|
19
|
+
readonly app_id: Schema.refine<import("./index.js").AppId, Schema.String>;
|
|
20
|
+
readonly team_id: Schema.String;
|
|
21
|
+
readonly stable_route: Schema.refine<import("./index.js").StableRoute, Schema.String>;
|
|
22
|
+
readonly route_key: Schema.String;
|
|
23
|
+
readonly reason: Schema.String;
|
|
24
|
+
readonly initiated_by: Schema.String;
|
|
25
|
+
readonly created_at: Schema.Number;
|
|
26
|
+
readonly removal_operation_id: Schema.NullOr<Schema.String>;
|
|
27
|
+
readonly state: Schema.Literals<readonly ["pending", "complete", "superseded-by-erasure"]>;
|
|
28
|
+
readonly completed_at: Schema.NullOr<Schema.Number>;
|
|
29
|
+
}>;
|
|
30
|
+
export type PublicRouteOperation = Schema.Schema.Type<typeof operationSchema>;
|
|
31
|
+
export type PublicRouteModerationResult = {
|
|
32
|
+
readonly kind: "recorded";
|
|
33
|
+
readonly operation: PublicRouteOperation;
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: "conflict" | "unavailable";
|
|
36
|
+
};
|
|
37
|
+
/** Publisher phases only. Its Operator composes the Marketplace phase between prepare and complete. */
|
|
38
|
+
export declare function createD1PublicRouteModeration(database: AppReleaseD1Database, now?: () => number): {
|
|
39
|
+
prepareRemoval: (input: PublicRouteRemoval) => Promise<PublicRouteModerationResult>;
|
|
40
|
+
prepareReallow: (input: PublicRouteRemoval, removalId: string) => Promise<PublicRouteModerationResult>;
|
|
41
|
+
complete: (operationId: string, operatorId: string) => Promise<PublicRouteModerationResult>;
|
|
42
|
+
pending: (after?: string) => Promise<{
|
|
43
|
+
readonly operation_id: string;
|
|
44
|
+
readonly kind: "unlist-and-release" | "re-allow";
|
|
45
|
+
readonly public_surface_id: import("./index.js").PublicSurfaceId;
|
|
46
|
+
readonly app_id: import("./index.js").AppId;
|
|
47
|
+
readonly team_id: string;
|
|
48
|
+
readonly stable_route: import("./index.js").StableRoute;
|
|
49
|
+
readonly route_key: string;
|
|
50
|
+
readonly reason: string;
|
|
51
|
+
readonly initiated_by: string;
|
|
52
|
+
readonly created_at: number;
|
|
53
|
+
readonly removal_operation_id: string | null;
|
|
54
|
+
readonly state: "complete" | "pending" | "superseded-by-erasure";
|
|
55
|
+
readonly completed_at: number | null;
|
|
56
|
+
}[]>;
|
|
57
|
+
read: (operationId: string) => Promise<{
|
|
58
|
+
readonly operation_id: string;
|
|
59
|
+
readonly kind: "unlist-and-release" | "re-allow";
|
|
60
|
+
readonly public_surface_id: import("./index.js").PublicSurfaceId;
|
|
61
|
+
readonly app_id: import("./index.js").AppId;
|
|
62
|
+
readonly team_id: string;
|
|
63
|
+
readonly stable_route: import("./index.js").StableRoute;
|
|
64
|
+
readonly route_key: string;
|
|
65
|
+
readonly reason: string;
|
|
66
|
+
readonly initiated_by: string;
|
|
67
|
+
readonly created_at: number;
|
|
68
|
+
readonly removal_operation_id: string | null;
|
|
69
|
+
readonly state: "complete" | "pending" | "superseded-by-erasure";
|
|
70
|
+
readonly completed_at: number | null;
|
|
71
|
+
} | null>;
|
|
72
|
+
};
|
|
73
|
+
declare const platformReplaySchema: Schema.Struct<{
|
|
74
|
+
readonly publicSurfaceId: Schema.refine<import("./index.js").PublicSurfaceId, Schema.String>;
|
|
75
|
+
readonly appId: Schema.refine<import("./index.js").AppId, Schema.String>;
|
|
76
|
+
readonly stableRoute: Schema.refine<import("./index.js").StableRoute, Schema.String>;
|
|
77
|
+
readonly teamId: Schema.String;
|
|
78
|
+
readonly operatorId: Schema.String;
|
|
79
|
+
}>;
|
|
80
|
+
export type PlatformOwnerReplay = Schema.Schema.Type<typeof platformReplaySchema>;
|
|
81
|
+
/** Private Operator door only: Hub supplies the resolved System Team and admitted actor. */
|
|
82
|
+
export declare function recordD1PlatformOwnerReplay(database: AppReleaseD1Database, raw: PlatformOwnerReplay): Promise<boolean>;
|
|
83
|
+
export {};
|