@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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +443 -2
  3. package/dist/app-api.d.ts +7 -0
  4. package/dist/app-api.js +61 -0
  5. package/dist/app-backend-binding-vocabulary.d.ts +299 -0
  6. package/dist/app-backend-binding-vocabulary.js +257 -0
  7. package/dist/app-backend-bindings.d.ts +21 -0
  8. package/dist/app-backend-bindings.js +47 -0
  9. package/dist/app-declaration.d.ts +46 -0
  10. package/dist/app-declaration.js +115 -0
  11. package/dist/app-event-facts.d.ts +40 -0
  12. package/dist/app-event-facts.js +28 -0
  13. package/dist/app-method-context.d.ts +109 -0
  14. package/dist/app-method-context.js +9 -0
  15. package/dist/app-method-runtime.d.ts +86 -0
  16. package/dist/app-method-runtime.js +122 -0
  17. package/dist/app-origin-paths.d.ts +6 -0
  18. package/dist/app-origin-paths.js +6 -0
  19. package/dist/app-vocabulary.d.ts +189 -0
  20. package/dist/app-vocabulary.js +103 -0
  21. package/dist/app.d.ts +379 -0
  22. package/dist/app.js +71 -0
  23. package/dist/artifact-dispositions.d.ts +115 -0
  24. package/dist/artifact-dispositions.js +234 -0
  25. package/dist/assets.d.ts +54 -0
  26. package/dist/assets.js +89 -0
  27. package/dist/backend.d.ts +41 -0
  28. package/dist/backend.js +29 -0
  29. package/dist/background-replay.d.ts +115 -0
  30. package/dist/background-replay.js +175 -0
  31. package/dist/buyer-references.d.ts +69 -0
  32. package/dist/buyer-references.js +154 -0
  33. package/dist/cloudflare-assets.d.ts +48 -0
  34. package/dist/cloudflare-assets.js +105 -0
  35. package/dist/cloudflare.d.ts +254 -0
  36. package/dist/cloudflare.js +1619 -0
  37. package/dist/effect.d.ts +73 -0
  38. package/dist/effect.js +325 -0
  39. package/dist/git-representation.d.ts +13 -0
  40. package/dist/git-representation.js +16 -0
  41. package/dist/inbox.d.ts +132 -0
  42. package/dist/inbox.js +81 -0
  43. package/dist/index.d.ts +31 -0
  44. package/dist/index.js +85 -0
  45. package/dist/internal/app-description.d.ts +14 -0
  46. package/dist/internal/app-description.js +98 -0
  47. package/dist/internal/app-release-artifact-reader.d.ts +26 -0
  48. package/dist/internal/app-release-artifact-reader.js +1 -0
  49. package/dist/internal/app-release-contract.d.ts +540 -0
  50. package/dist/internal/app-release-contract.js +169 -0
  51. package/dist/internal/app-release-schema.d.ts +1676 -0
  52. package/dist/internal/app-release-schema.js +386 -0
  53. package/dist/internal/app-release-store.d.ts +47 -0
  54. package/dist/internal/app-release-store.js +1 -0
  55. package/dist/internal/background-sync.d.ts +7 -0
  56. package/dist/internal/background-sync.js +10 -0
  57. package/dist/internal/deterministic-memory-app-release-store.d.ts +14 -0
  58. package/dist/internal/deterministic-memory-app-release-store.js +319 -0
  59. package/dist/internal/failure.d.ts +2 -0
  60. package/dist/internal/failure.js +9 -0
  61. package/dist/internal/immutable-release-material.d.ts +3 -0
  62. package/dist/internal/immutable-release-material.js +13 -0
  63. package/dist/internal/promise-app-release-store-adapter.d.ts +9 -0
  64. package/dist/internal/promise-app-release-store-adapter.js +65 -0
  65. package/dist/internal/promise-artifact-reader-adapter.d.ts +9 -0
  66. package/dist/internal/promise-artifact-reader-adapter.js +18 -0
  67. package/dist/internal/public-route-sql.d.ts +10 -0
  68. package/dist/internal/public-route-sql.js +239 -0
  69. package/dist/internal/public-website-authority.d.ts +338 -0
  70. package/dist/internal/public-website-authority.js +50 -0
  71. package/dist/internal/public-website-delivery.d.ts +59 -0
  72. package/dist/internal/public-website-delivery.js +232 -0
  73. package/dist/internal/public-website-fetch.d.ts +8 -0
  74. package/dist/internal/public-website-fetch.js +54 -0
  75. package/dist/internal/release-transition-material.d.ts +3 -0
  76. package/dist/internal/release-transition-material.js +11 -0
  77. package/dist/internal/verified-artifact-snapshot.d.ts +41 -0
  78. package/dist/internal/verified-artifact-snapshot.js +91 -0
  79. package/dist/public-route-moderation.d.ts +83 -0
  80. package/dist/public-route-moderation.js +236 -0
  81. package/dist/public-route-policy.d.ts +51 -0
  82. package/dist/public-route-policy.js +88 -0
  83. package/dist/public-routes.d.ts +34 -0
  84. package/dist/public-routes.js +136 -0
  85. package/dist/publisher.d.ts +61 -0
  86. package/dist/publisher.js +16 -0
  87. package/dist/safety-takedown.d.ts +56 -0
  88. package/dist/safety-takedown.js +146 -0
  89. package/dist/seller-closure-notice.d.ts +14 -0
  90. package/dist/seller-closure-notice.js +14 -0
  91. package/dist/seller-closure.d.ts +15 -0
  92. package/dist/seller-closure.js +31 -0
  93. package/dist/testing.d.ts +8 -0
  94. package/dist/testing.js +47 -0
  95. package/docs/api/README.md +26 -0
  96. package/docs/api/app-backend-binding-vocabulary.md +276 -0
  97. package/docs/api/app-backend-bindings.md +141 -0
  98. package/docs/api/app-event-facts.md +110 -0
  99. package/docs/api/app-method-context.md +166 -0
  100. package/docs/api/app-method-runtime.md +330 -0
  101. package/docs/api/backend.md +108 -0
  102. package/docs/api/background-replay.md +516 -0
  103. package/docs/api/cloudflare.md +1044 -0
  104. package/docs/api/effect.md +2429 -0
  105. package/docs/api/git-representation.md +56 -0
  106. package/docs/api/inbox.md +289 -0
  107. package/docs/api/index.md +2868 -0
  108. package/docs/api/publisher.md +147 -0
  109. package/docs/api/testing.md +31 -0
  110. package/package.json +158 -3
@@ -0,0 +1,236 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { appIdSchema, publicSurfaceIdSchema, stableRouteSchema, } from "./internal/app-release-schema.js";
3
+ import { publicRouteKey, publicRoutePolicy } from "./public-route-policy.js";
4
+ import { reportFailure } from "./internal/failure.js";
5
+ const identity = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(256)));
6
+ const commandSchema = Schema.Struct({
7
+ operationId: identity,
8
+ operatorId: identity,
9
+ reason: Schema.String.pipe(Schema.check(Schema.isPattern(/\S/u)), Schema.check(Schema.isMaxLength(1000))),
10
+ target: Schema.Struct({
11
+ publicSurfaceId: publicSurfaceIdSchema,
12
+ appId: appIdSchema,
13
+ teamId: identity,
14
+ stableRoute: stableRouteSchema,
15
+ }),
16
+ });
17
+ const operationSchema = Schema.Struct({
18
+ operation_id: identity,
19
+ kind: Schema.Literals(["unlist-and-release", "re-allow"]),
20
+ public_surface_id: publicSurfaceIdSchema,
21
+ app_id: appIdSchema,
22
+ team_id: identity,
23
+ stable_route: stableRouteSchema,
24
+ route_key: Schema.String,
25
+ reason: Schema.String,
26
+ initiated_by: identity,
27
+ created_at: Schema.Number,
28
+ removal_operation_id: Schema.NullOr(identity),
29
+ state: Schema.Literals(["pending", "complete", "superseded-by-erasure"]),
30
+ completed_at: Schema.NullOr(Schema.Number),
31
+ });
32
+ /** Publisher phases only. Its Operator composes the Marketplace phase between prepare and complete. */
33
+ export function createD1PublicRouteModeration(database, now = Date.now) {
34
+ const read = async (operationId) => {
35
+ const row = await database
36
+ .prepare("SELECT * FROM app_release_route_operations WHERE operation_id=?")
37
+ .bind(operationId)
38
+ .first();
39
+ return row === null ? null : Schema.decodeUnknownSync(operationSchema)(row);
40
+ };
41
+ const protect = async (action) => {
42
+ try {
43
+ return await action();
44
+ }
45
+ catch (cause) {
46
+ reportFailure("public route moderation", cause);
47
+ return { kind: "unavailable" };
48
+ }
49
+ };
50
+ const matches = (operation, input, kind, removalId) => operation.kind === kind &&
51
+ operation.public_surface_id === input.target.publicSurfaceId &&
52
+ operation.app_id === input.target.appId &&
53
+ operation.team_id === input.target.teamId &&
54
+ operation.stable_route === input.target.stableRoute &&
55
+ operation.reason === input.reason &&
56
+ operation.removal_operation_id === removalId;
57
+ const prepare = (raw, removalId) => protect(async () => {
58
+ const input = Schema.decodeUnknownSync(commandSchema, {
59
+ onExcessProperty: "error",
60
+ })(raw);
61
+ const kind = removalId === null ? "unlist-and-release" : "re-allow";
62
+ const existing = await read(input.operationId);
63
+ if (existing)
64
+ return matches(existing, input, kind, removalId) &&
65
+ existing.state !== "superseded-by-erasure"
66
+ ? { kind: "recorded", operation: existing }
67
+ : { kind: "conflict" };
68
+ const at = Math.floor(now() / 1000);
69
+ const key = publicRouteKey(input.target.stableRoute);
70
+ const material = [
71
+ input.operationId,
72
+ kind,
73
+ input.target.publicSurfaceId,
74
+ input.target.appId,
75
+ input.target.teamId,
76
+ input.target.stableRoute,
77
+ key,
78
+ input.reason,
79
+ input.operatorId,
80
+ at,
81
+ removalId,
82
+ ];
83
+ const eligible = removalId === null
84
+ ? `EXISTS (SELECT 1 FROM app_release_route_claims c
85
+ JOIN app_release_public_surfaces s ON s.public_surface_id=c.public_surface_id
86
+ WHERE c.public_surface_id=? AND s.app_id=? AND c.team_id=? AND c.stable_route=? AND c.state='held')`
87
+ : `EXISTS (SELECT 1 FROM app_release_team_route_blocks b
88
+ JOIN app_release_route_operations removal ON removal.operation_id=b.removal_operation_id
89
+ WHERE removal.public_surface_id=? AND removal.app_id=? AND b.team_id=? AND removal.stable_route=?
90
+ AND b.removal_operation_id=? AND removal.state='complete')`;
91
+ const guardValues = [
92
+ input.target.publicSurfaceId,
93
+ input.target.appId,
94
+ input.target.teamId,
95
+ input.target.stableRoute,
96
+ ...(removalId === null ? [] : [removalId]),
97
+ ];
98
+ // Every mutation joins the full bound material, so a racing changed-ID replay cannot advance it.
99
+ const guard = `SELECT operation_id FROM app_release_route_operations
100
+ WHERE operation_id=? AND kind=? AND public_surface_id=? AND app_id=? AND team_id=?
101
+ AND stable_route=? AND route_key=? AND reason=? AND state='pending'
102
+ AND removal_operation_id IS ?`;
103
+ const guardBindings = [...material.slice(0, 8), removalId];
104
+ const statements = [
105
+ database
106
+ .prepare(`INSERT INTO app_release_route_operations
107
+ (operation_id,kind,public_surface_id,app_id,team_id,stable_route,route_key,reason,initiated_by,created_at,removal_operation_id,state)
108
+ SELECT ?,?,?,?,?,?,?,?,?,?,?,'pending' WHERE ${eligible} ON CONFLICT DO NOTHING`)
109
+ .bind(...material, ...guardValues),
110
+ ];
111
+ if (removalId === null)
112
+ statements.push(database
113
+ .prepare(`UPDATE app_release_route_claims SET state='removing',removal_operation_id=?
114
+ WHERE public_surface_id=? AND state='held' AND EXISTS (${guard})`)
115
+ .bind(input.operationId, input.target.publicSurfaceId, ...guardBindings), database
116
+ .prepare(`INSERT INTO app_release_surface_retirements(public_surface_id,operation_id)
117
+ SELECT public_surface_id,operation_id FROM app_release_route_operations WHERE operation_id IN (${guard})
118
+ ON CONFLICT DO NOTHING`)
119
+ .bind(...guardBindings), database
120
+ .prepare(`INSERT INTO app_release_team_route_blocks(team_id,route_key,stable_route,removal_operation_id)
121
+ SELECT team_id,route_key,stable_route,operation_id FROM app_release_route_operations WHERE operation_id IN (${guard})
122
+ ON CONFLICT(team_id,route_key) DO UPDATE SET removal_operation_id=excluded.removal_operation_id,stable_route=excluded.stable_route`)
123
+ .bind(...guardBindings));
124
+ statements.push(database
125
+ .prepare(`INSERT INTO app_release_route_operation_steps(operation_id,phase,operator_id,recorded_at)
126
+ SELECT operation_id,'prepared',?,? FROM app_release_route_operations WHERE operation_id IN (${guard}) ON CONFLICT DO NOTHING`)
127
+ .bind(input.operatorId, at, ...guardBindings));
128
+ const results = await database.batch(statements);
129
+ if (results.length !== statements.length ||
130
+ results.some((result) => !result.success))
131
+ throw new Error("Route moderation batch unavailable");
132
+ const operation = await read(input.operationId);
133
+ return operation && matches(operation, input, kind, removalId)
134
+ ? { kind: "recorded", operation }
135
+ : { kind: "conflict" };
136
+ });
137
+ /** Call only after the corresponding Marketplace owner returns its committed phase. */
138
+ const complete = (operationId, operatorId) => protect(async () => {
139
+ const id = Schema.decodeUnknownSync(identity)(operationId);
140
+ const actor = Schema.decodeUnknownSync(identity)(operatorId);
141
+ const operation = await read(id);
142
+ if (!operation || operation.state === "superseded-by-erasure")
143
+ return { kind: "conflict" };
144
+ if (operation.state === "complete")
145
+ return { kind: "recorded", operation };
146
+ const at = Math.floor(now() / 1000);
147
+ const statements = [];
148
+ if (operation.kind === "unlist-and-release") {
149
+ // The exact still-removing claim is the only source of a new hold. A stale replay cannot freeze a later claimant.
150
+ statements.push(database
151
+ .prepare(`INSERT INTO app_release_route_holds(route_key,stable_route,until_s,cause,operation_id)
152
+ SELECT c.route_key,c.stable_route,?,'operator-release',o.operation_id
153
+ FROM app_release_route_claims c JOIN app_release_route_operations o
154
+ ON o.operation_id=c.removal_operation_id AND o.public_surface_id=c.public_surface_id
155
+ WHERE o.operation_id=? AND o.state='pending' AND c.state='removing'
156
+ ON CONFLICT(route_key) DO UPDATE SET until_s=max(until_s,excluded.until_s),
157
+ cause=CASE WHEN cause='erasure' THEN cause ELSE excluded.cause END,
158
+ operation_id=CASE WHEN cause='erasure' THEN operation_id ELSE excluded.operation_id END`)
159
+ .bind(at + publicRoutePolicy.releaseCooldownSeconds, id));
160
+ statements.push(database
161
+ .prepare(`DELETE FROM app_release_route_claims
162
+ WHERE public_surface_id=? AND state='removing' AND removal_operation_id=?
163
+ AND EXISTS (SELECT 1 FROM app_release_route_holds h WHERE h.route_key=app_release_route_claims.route_key AND h.until_s>=?)`)
164
+ .bind(operation.public_surface_id, id, at + publicRoutePolicy.releaseCooldownSeconds));
165
+ }
166
+ else
167
+ statements.push(database
168
+ .prepare(`DELETE FROM app_release_team_route_blocks
169
+ WHERE team_id=? AND route_key=? AND removal_operation_id=?`)
170
+ .bind(operation.team_id, operation.route_key, operation.removal_operation_id));
171
+ const completed = operation.kind === "unlist-and-release"
172
+ ? `NOT EXISTS (SELECT 1 FROM app_release_route_claims c WHERE c.public_surface_id=app_release_route_operations.public_surface_id)
173
+ AND EXISTS (SELECT 1 FROM app_release_route_holds h WHERE h.route_key=app_release_route_operations.route_key AND h.operation_id=app_release_route_operations.operation_id)`
174
+ : `NOT EXISTS (SELECT 1 FROM app_release_team_route_blocks b WHERE b.team_id=app_release_route_operations.team_id AND b.route_key=app_release_route_operations.route_key)`;
175
+ statements.push(database
176
+ .prepare(`UPDATE app_release_route_operations SET state='complete',completed_at=?
177
+ WHERE operation_id=? AND state='pending' AND ${completed}`)
178
+ .bind(at, id));
179
+ statements.push(database
180
+ .prepare(`INSERT INTO app_release_route_operation_steps(operation_id,phase,operator_id,recorded_at)
181
+ SELECT operation_id,'completed',?,? FROM app_release_route_operations WHERE operation_id=? AND state='complete'
182
+ ON CONFLICT DO NOTHING`)
183
+ .bind(actor, at, id));
184
+ const results = await database.batch(statements);
185
+ if (results.length !== statements.length ||
186
+ results.some((result) => !result.success))
187
+ throw new Error("Route moderation batch unavailable");
188
+ const result = await read(id);
189
+ return result?.state === "complete"
190
+ ? { kind: "recorded", operation: result }
191
+ : { kind: "conflict" };
192
+ });
193
+ const pending = async (after = "") => {
194
+ const cursor = Schema.decodeUnknownSync(Schema.String.pipe(Schema.check(Schema.isMaxLength(256))))(after);
195
+ const result = await database
196
+ .prepare("SELECT * FROM app_release_route_operations WHERE state='pending' AND operation_id>? ORDER BY operation_id LIMIT 100")
197
+ .bind(cursor)
198
+ .all();
199
+ return result.results.map((row) => Schema.decodeUnknownSync(operationSchema)(row));
200
+ };
201
+ return {
202
+ prepareRemoval: (input) => prepare(input, null),
203
+ prepareReallow: (input, removalId) => prepare(input, Schema.decodeUnknownSync(identity)(removalId)),
204
+ complete,
205
+ pending,
206
+ read,
207
+ };
208
+ }
209
+ const platformReplaySchema = Schema.Struct({
210
+ publicSurfaceId: publicSurfaceIdSchema,
211
+ appId: appIdSchema,
212
+ stableRoute: stableRouteSchema,
213
+ teamId: identity,
214
+ operatorId: identity,
215
+ });
216
+ /** Private Operator door only: Hub supplies the resolved System Team and admitted actor. */
217
+ export async function recordD1PlatformOwnerReplay(database, raw) {
218
+ const input = Schema.decodeUnknownSync(platformReplaySchema)(raw);
219
+ const row = await database
220
+ .prepare(`
221
+ INSERT INTO app_release_platform_owner_replays
222
+ (public_surface_id,app_id,stable_route,owner_team_id,publisher_user_id,operator_user_id,recorded_at)
223
+ SELECT s.public_surface_id,s.app_id,s.stable_route,s.publisher_team_id,s.publisher_user_id,?,unixepoch()
224
+ FROM app_release_public_surfaces s
225
+ JOIN app_release_route_claims c ON c.public_surface_id=s.public_surface_id AND c.route_key=s.route_key
226
+ JOIN app_release_platform_routes p ON p.app_id=s.app_id AND p.stable_route=s.stable_route AND p.route_key=s.route_key
227
+ WHERE s.public_surface_id=? AND s.app_id=? AND s.stable_route=?
228
+ AND s.publisher_team_id=? AND c.team_id=s.publisher_team_id
229
+ AND c.user_id=s.publisher_user_id AND c.state='held' AND s.authority_kind='platform'
230
+ AND s.publisher_user_id<>?
231
+ AND NOT EXISTS (SELECT 1 FROM app_release_surface_retirements x WHERE x.public_surface_id=s.public_surface_id)
232
+ RETURNING public_surface_id`)
233
+ .bind(input.operatorId, input.publicSurfaceId, input.appId, input.stableRoute, input.teamId, input.operatorId)
234
+ .first();
235
+ return row !== null;
236
+ }
@@ -0,0 +1,51 @@
1
+ import * as Schema from "effect/Schema";
2
+ /** CTO customer-publication policy. Deployment projects this one configuration. */
3
+ export declare const publicRoutePolicy: {
4
+ readonly version: 1;
5
+ readonly maxPublicRoutesPerTeam: 20;
6
+ readonly maxUnusedSurfacesPerApp: 20;
7
+ readonly releaseCooldownSeconds: number;
8
+ readonly erasureTombstoneSeconds: number;
9
+ readonly reservedWords: readonly ["admin", "administrator", "api", "assets", "auth", "billing", "botanical", "docs", "fungi", "help", "login", "logout", "oauth", "operator", "security", "settings", "signin", "signup", "static", "status", "support", "www"];
10
+ readonly brandPrefixes: readonly ["fungi"];
11
+ readonly brandWordExceptions: readonly ["fungible", "fungicide"];
12
+ };
13
+ /** Comparison only: a key never repairs or replaces the canonical route URL. */
14
+ export declare function publicRouteKey(value: string): string;
15
+ /** Router prefixes and platform assignments are supplied by their owners. */
16
+ export declare function isReservedPublicRoute(route: string, structuralRoutes: readonly string[]): boolean;
17
+ /** Deployment supplies routes from the platform plan and App-origin router. */
18
+ export declare const publicRouteConfigurationSchema: Schema.Struct<{
19
+ readonly platformRoutes: Schema.$Array<Schema.Struct<{
20
+ readonly appId: Schema.refine<import("./index.js").AppId, Schema.String>;
21
+ readonly stableRoute: Schema.refine<import("./index.js").StableRoute, Schema.String>;
22
+ }>>;
23
+ readonly structuralRoutes: Schema.$Array<Schema.refine<import("./index.js").StableRoute, Schema.String>>;
24
+ }>;
25
+ export type PublicRouteConfiguration = Schema.Schema.Type<typeof publicRouteConfigurationSchema>;
26
+ /** The deployed checksum binds both maintained policy and environment assignments. */
27
+ export declare function capturePublicRoutePolicy(input: unknown): Promise<{
28
+ configuration: {
29
+ platformRoutes: {
30
+ readonly appId: import("./index.js").AppId;
31
+ readonly stableRoute: import("./index.js").StableRoute;
32
+ }[];
33
+ structuralRoutes: import("./index.js").StableRoute[];
34
+ };
35
+ json: string;
36
+ digest: string;
37
+ version: 1;
38
+ }>;
39
+ /** Decode the deployment JSON at its owned representation boundary. */
40
+ export declare function capturePublicRoutePolicyFromJson(input: string): Promise<{
41
+ configuration: {
42
+ platformRoutes: {
43
+ readonly appId: import("./index.js").AppId;
44
+ readonly stableRoute: import("./index.js").StableRoute;
45
+ }[];
46
+ structuralRoutes: import("./index.js").StableRoute[];
47
+ };
48
+ json: string;
49
+ digest: string;
50
+ version: 1;
51
+ }>;
@@ -0,0 +1,88 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { appIdSchema, stableRouteSchema, } from "./internal/app-release-schema.js";
3
+ /** CTO customer-publication policy. Deployment projects this one configuration. */
4
+ export const publicRoutePolicy = {
5
+ version: 1,
6
+ maxPublicRoutesPerTeam: 20,
7
+ maxUnusedSurfacesPerApp: 20,
8
+ releaseCooldownSeconds: 7 * 24 * 60 * 60,
9
+ erasureTombstoneSeconds: 90 * 24 * 60 * 60,
10
+ reservedWords: [
11
+ "admin",
12
+ "administrator",
13
+ "api",
14
+ "assets",
15
+ "auth",
16
+ "billing",
17
+ "botanical",
18
+ "docs",
19
+ "fungi",
20
+ "help",
21
+ "login",
22
+ "logout",
23
+ "oauth",
24
+ "operator",
25
+ "security",
26
+ "settings",
27
+ "signin",
28
+ "signup",
29
+ "static",
30
+ "status",
31
+ "support",
32
+ "www",
33
+ ],
34
+ brandPrefixes: ["fungi"],
35
+ brandWordExceptions: ["fungible", "fungicide"],
36
+ };
37
+ /** Comparison only: a key never repairs or replaces the canonical route URL. */
38
+ export function publicRouteKey(value) {
39
+ return (value.startsWith("/") ? value.slice(1) : value)
40
+ .replaceAll("-", "")
41
+ .replaceAll("_", "")
42
+ .replaceAll("1", "i")
43
+ .replaceAll("l", "i")
44
+ .replaceAll("0", "o")
45
+ .replaceAll("5", "s")
46
+ .replaceAll("3", "e");
47
+ }
48
+ /** Router prefixes and platform assignments are supplied by their owners. */
49
+ export function isReservedPublicRoute(route, structuralRoutes) {
50
+ const key = publicRouteKey(route);
51
+ if (structuralRoutes.some((value) => publicRouteKey(value) === key) ||
52
+ publicRoutePolicy.reservedWords.some((value) => publicRouteKey(value) === key))
53
+ return true;
54
+ return (publicRoutePolicy.brandPrefixes.some((value) => key.startsWith(publicRouteKey(value))) &&
55
+ !publicRoutePolicy.brandWordExceptions.some((value) => publicRouteKey(value) === key));
56
+ }
57
+ /** Deployment supplies routes from the platform plan and App-origin router. */
58
+ export const publicRouteConfigurationSchema = Schema.Struct({
59
+ platformRoutes: Schema.Array(Schema.Struct({
60
+ appId: appIdSchema,
61
+ stableRoute: stableRouteSchema,
62
+ })),
63
+ structuralRoutes: Schema.Array(stableRouteSchema),
64
+ }).pipe(Schema.check(Schema.makeFilter((configuration) => {
65
+ const keys = configuration.platformRoutes.map(({ stableRoute }) => publicRouteKey(stableRoute));
66
+ return new Set(keys).size === keys.length;
67
+ }, { message: "Platform route assignments conflict" })), Schema.check(Schema.makeFilter((configuration) => {
68
+ const structuralKeys = new Set(configuration.structuralRoutes.map(publicRouteKey));
69
+ return !configuration.platformRoutes.some(({ stableRoute }) => structuralKeys.has(publicRouteKey(stableRoute)));
70
+ }, { message: "Platform route assignments conflict with structural routes" })));
71
+ /** The deployed checksum binds both maintained policy and environment assignments. */
72
+ export async function capturePublicRoutePolicy(input) {
73
+ const decoded = Schema.decodeUnknownSync(publicRouteConfigurationSchema, {
74
+ onExcessProperty: "error",
75
+ })(input);
76
+ const configuration = {
77
+ platformRoutes: [...decoded.platformRoutes].sort((a, b) => a.stableRoute.localeCompare(b.stableRoute)),
78
+ structuralRoutes: [...decoded.structuralRoutes].sort(),
79
+ };
80
+ const json = JSON.stringify({ policy: publicRoutePolicy, configuration });
81
+ const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(json)));
82
+ const digest = Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
83
+ return { configuration, json, digest, version: publicRoutePolicy.version };
84
+ }
85
+ /** Decode the deployment JSON at its owned representation boundary. */
86
+ export async function capturePublicRoutePolicyFromJson(input) {
87
+ return capturePublicRoutePolicy(Schema.decodeUnknownSync(Schema.fromJsonString(publicRouteConfigurationSchema), { onExcessProperty: "error" })(input));
88
+ }
@@ -0,0 +1,34 @@
1
+ import * as Schema from "effect/Schema";
2
+ import type { AppReleaseD1Database } from "./cloudflare.js";
3
+ import { capturePublicRoutePolicy } from "./public-route-policy.js";
4
+ export type CapturedPublicRoutePolicy = Awaited<ReturnType<typeof capturePublicRoutePolicy>>;
5
+ /** Fail closed until the operator has installed this exact deployment policy. */
6
+ export declare function publicRoutePolicyReady(database: AppReleaseD1Database, policy: CapturedPublicRoutePolicy): Promise<boolean>;
7
+ /** Operator composition only; values come from its captured deployment, not a request. */
8
+ export declare function configureD1PublicRoutes(database: AppReleaseD1Database, policy: CapturedPublicRoutePolicy): Promise<void>;
9
+ /** Live seller custody; a buyer entitlement or a historical Surface is never ownership. */
10
+ export declare function ownsD1PublicRoute(database: AppReleaseD1Database, input: {
11
+ readonly teamId: string;
12
+ readonly appId: string;
13
+ readonly stableRoute: string;
14
+ }): Promise<boolean>;
15
+ declare const ownedPublicRouteSchema: Schema.Struct<{
16
+ readonly appId: Schema.refine<import("./index.js").AppId, Schema.String>;
17
+ readonly publicSurfaceId: Schema.refine<import("./index.js").PublicSurfaceId, Schema.String>;
18
+ readonly stableRoute: Schema.refine<import("./index.js").StableRoute, Schema.String>;
19
+ readonly releaseId: Schema.refine<import("./index.js").ReleaseId, Schema.String>;
20
+ }>;
21
+ export type OwnedPublicRoute = Schema.Schema.Type<typeof ownedPublicRouteSchema>;
22
+ /** Published customer Surfaces still held by this Team, with their current promotion or rollback. */
23
+ export declare function listD1OwnedPublicRoutes(database: AppReleaseD1Database, teamId: string): Promise<readonly OwnedPublicRoute[]>;
24
+ /** Catalog-local irreversible closure; public admission never depends on Global availability. */
25
+ export declare function closedD1PublicRoute(input: {
26
+ catalog: AppReleaseD1Database;
27
+ route: string;
28
+ }): Promise<boolean>;
29
+ /** A platform artifact keeps its public mount; a customer claim never qualifies. */
30
+ export declare function platformD1PublicRoute(database: AppReleaseD1Database, input: {
31
+ appId: string;
32
+ stableRoute: string;
33
+ }): Promise<boolean>;
34
+ export {};
@@ -0,0 +1,136 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { appIdSchema, publicSurfaceIdSchema, releaseIdSchema, stableRouteSchema, } from "./internal/app-release-schema.js";
3
+ import { capturePublicRoutePolicy, publicRouteKey, publicRoutePolicy, } from "./public-route-policy.js";
4
+ const policyRowSchema = Schema.Struct({
5
+ version: Schema.Number,
6
+ digest: Schema.String,
7
+ });
8
+ /** Fail closed until the operator has installed this exact deployment policy. */
9
+ export async function publicRoutePolicyReady(database, policy) {
10
+ const row = await database
11
+ .prepare("SELECT version,digest FROM app_release_route_policy WHERE singleton=1")
12
+ .first();
13
+ if (row === null)
14
+ return false;
15
+ const found = Schema.decodeUnknownSync(policyRowSchema)(row);
16
+ return found.version === policy.version && found.digest === policy.digest;
17
+ }
18
+ /** Operator composition only; values come from its captured deployment, not a request. */
19
+ export async function configureD1PublicRoutes(database, policy) {
20
+ if (await publicRoutePolicyReady(database, policy))
21
+ return;
22
+ const exact = new Map();
23
+ for (const word of [
24
+ ...publicRoutePolicy.reservedWords,
25
+ ...policy.configuration.structuralRoutes,
26
+ ])
27
+ exact.set(publicRouteKey(word), word);
28
+ const statements = [
29
+ database.prepare("DELETE FROM app_release_reserved_route_keys"),
30
+ database.prepare("DELETE FROM app_release_route_brand_exceptions"),
31
+ database.prepare("DELETE FROM app_release_platform_routes"),
32
+ ...[...exact].map(([key, word]) => database
33
+ .prepare("INSERT INTO app_release_reserved_route_keys(word,route_key,match_kind) VALUES (?,?,'exact')")
34
+ .bind(word, key)),
35
+ ...publicRoutePolicy.brandPrefixes.map((word) => database
36
+ .prepare("INSERT INTO app_release_reserved_route_keys(word,route_key,match_kind) VALUES (?,?,'prefix')")
37
+ .bind(word, publicRouteKey(word))),
38
+ ...publicRoutePolicy.brandWordExceptions.map((word) => database
39
+ .prepare("INSERT INTO app_release_route_brand_exceptions(word,route_key) VALUES (?,?)")
40
+ .bind(word, publicRouteKey(word))),
41
+ ...policy.configuration.platformRoutes.map(({ appId, stableRoute }) => database
42
+ .prepare("INSERT INTO app_release_platform_routes(stable_route,route_key,app_id) VALUES (?,?,?)")
43
+ .bind(stableRoute, publicRouteKey(stableRoute), appId)),
44
+ database
45
+ .prepare(`INSERT INTO app_release_route_policy
46
+ (singleton,version,digest,config_json,max_team_routes,max_unused_surfaces_per_app)
47
+ VALUES (1,?,?,?,?,?) ON CONFLICT(singleton) DO UPDATE SET
48
+ version=excluded.version,digest=excluded.digest,config_json=excluded.config_json,
49
+ max_team_routes=excluded.max_team_routes,max_unused_surfaces_per_app=excluded.max_unused_surfaces_per_app`)
50
+ .bind(policy.version, policy.digest, policy.json, publicRoutePolicy.maxPublicRoutesPerTeam, publicRoutePolicy.maxUnusedSurfacesPerApp),
51
+ ];
52
+ const result = await database.batch(statements);
53
+ if (result.length !== statements.length || result.some((row) => !row.success))
54
+ throw new Error("Public route policy projection unavailable");
55
+ }
56
+ const heldClaimSchema = Schema.Struct({
57
+ app_id: Schema.String,
58
+ public_surface_id: Schema.String,
59
+ team_id: Schema.String,
60
+ user_id: Schema.String,
61
+ stable_route: Schema.String,
62
+ });
63
+ const sellerRouteJoins = `FROM app_release_route_claims c
64
+ JOIN app_release_public_surfaces s ON s.public_surface_id=c.public_surface_id
65
+ JOIN app_release_team_publishers p ON p.app_id=s.app_id AND p.team_id=c.team_id`;
66
+ const sellerRouteCustody = `c.state='held' AND s.authority_kind='customer'
67
+ AND p.source_provider='artifacts' AND p.source_repository=p.app_id
68
+ AND p.template_app_id IS NOT NULL AND p.template_app_id<>p.app_id
69
+ AND p.template_seed_revision IS NOT NULL
70
+ AND NOT EXISTS (SELECT 1 FROM app_release_surface_retirements r WHERE r.public_surface_id=c.public_surface_id)
71
+ AND NOT EXISTS (SELECT 1 FROM app_release_team_route_blocks b WHERE b.team_id=c.team_id AND b.route_key=c.route_key)`;
72
+ /** Live seller custody; a buyer entitlement or a historical Surface is never ownership. */
73
+ export async function ownsD1PublicRoute(database, input) {
74
+ const row = await database
75
+ .prepare(`SELECT s.app_id,c.public_surface_id,c.team_id,c.user_id,c.stable_route
76
+ ${sellerRouteJoins}
77
+ WHERE c.route_key=? AND c.stable_route=? AND c.team_id=? AND s.app_id=?
78
+ AND ${sellerRouteCustody}`)
79
+ .bind(publicRouteKey(input.stableRoute), input.stableRoute, input.teamId, input.appId)
80
+ .first();
81
+ if (row === null)
82
+ return false;
83
+ Schema.decodeUnknownSync(heldClaimSchema)(row);
84
+ return true;
85
+ }
86
+ const ownedPublicRouteSchema = Schema.Struct({
87
+ appId: appIdSchema,
88
+ publicSurfaceId: publicSurfaceIdSchema,
89
+ stableRoute: stableRouteSchema,
90
+ releaseId: releaseIdSchema,
91
+ });
92
+ /** Published customer Surfaces still held by this Team, with their current promotion or rollback. */
93
+ export async function listD1OwnedPublicRoutes(database, teamId) {
94
+ const { results } = await database
95
+ .prepare(`SELECT s.app_id AS appId,c.public_surface_id AS publicSurfaceId,
96
+ c.stable_route AS stableRoute,t.release_id AS releaseId
97
+ ${sellerRouteJoins}
98
+ JOIN app_release_transitions t ON t.public_surface_id=c.public_surface_id
99
+ WHERE c.team_id=? AND ${sellerRouteCustody}
100
+ AND t.operation_kind IN ('promotion','rollback')
101
+ AND t.sequence=(SELECT MAX(latest.sequence) FROM app_release_transitions latest
102
+ WHERE latest.public_surface_id=c.public_surface_id AND latest.operation_kind IN ('promotion','rollback'))
103
+ ORDER BY s.app_id,c.public_surface_id`)
104
+ .bind(teamId)
105
+ .all();
106
+ return Schema.decodeUnknownSync(Schema.Array(ownedPublicRouteSchema))(results);
107
+ }
108
+ /** Catalog-local irreversible closure; public admission never depends on Global availability. */
109
+ export async function closedD1PublicRoute(input) {
110
+ const row = await input.catalog
111
+ .prepare(`SELECT c.team_id FROM app_release_route_claims c
112
+ JOIN app_release_public_surfaces s ON s.public_surface_id=c.public_surface_id
113
+ JOIN app_release_closed_sellers closed ON closed.team_id=c.team_id
114
+ WHERE c.route_key=? AND c.stable_route=? AND c.state='held' AND s.authority_kind='customer'`)
115
+ .bind(publicRouteKey(input.route), input.route)
116
+ .first();
117
+ if (row === null)
118
+ return false;
119
+ Schema.decodeUnknownSync(Schema.Struct({ team_id: Schema.String }))(row);
120
+ return true;
121
+ }
122
+ /** A platform artifact keeps its public mount; a customer claim never qualifies. */
123
+ export async function platformD1PublicRoute(database, input) {
124
+ const row = await database
125
+ .prepare(`SELECT s.app_id FROM app_release_public_surfaces s
126
+ JOIN app_release_platform_routes p ON p.app_id=s.app_id AND p.stable_route=s.stable_route
127
+ JOIN app_release_route_claims c ON c.public_surface_id=s.public_surface_id
128
+ WHERE s.app_id=? AND s.stable_route=? AND c.route_key=? AND c.stable_route=s.stable_route
129
+ AND c.state='held' AND s.authority_kind='platform'`)
130
+ .bind(input.appId, input.stableRoute, publicRouteKey(input.stableRoute))
131
+ .first();
132
+ if (row === null)
133
+ return false;
134
+ Schema.decodeUnknownSync(Schema.Struct({ app_id: Schema.String }))(row);
135
+ return true;
136
+ }
@@ -0,0 +1,61 @@
1
+ import * as Schema from "effect/Schema";
2
+ import type { AppId, SourceRevision } from "./internal/app-release-contract.js";
3
+ /** Canonical source repository resolved by the product publisher. */
4
+ export declare const appSourceRepositorySchema: Schema.Struct<{
5
+ readonly provider: Schema.Literals<readonly ["artifacts", "github"]>;
6
+ readonly repository: Schema.String;
7
+ readonly branch: Schema.Literal<"main">;
8
+ }>;
9
+ /** Team-owned publication authority for one separately registered App. */
10
+ export declare const teamAppPublisherRegistrationSchema: Schema.Struct<{
11
+ readonly appId: Schema.refine<AppId, Schema.String>;
12
+ readonly teamId: Schema.String;
13
+ readonly source: Schema.Struct<{
14
+ readonly provider: Schema.Literals<readonly ["artifacts", "github"]>;
15
+ readonly repository: Schema.String;
16
+ readonly branch: Schema.Literal<"main">;
17
+ }>;
18
+ readonly templateSeed: Schema.optionalKey<Schema.Struct<{
19
+ readonly appId: Schema.refine<AppId, Schema.String>;
20
+ readonly revision: Schema.refine<SourceRevision, Schema.String>;
21
+ }>>;
22
+ }>;
23
+ /** Exact first-party platform seed from which a Team-owned App was forked. */
24
+ export type TeamAppTemplateSeed = Readonly<{
25
+ appId: AppId;
26
+ revision: SourceRevision;
27
+ }>;
28
+ /** Parsed immutable source location used to build one App. */
29
+ export type AppSourceRepository = Schema.Schema.Type<typeof appSourceRepositorySchema>;
30
+ /** Parsed Team ownership record for one App publisher. */
31
+ export type TeamAppPublisherRegistration = Schema.Schema.Type<typeof teamAppPublisherRegistrationSchema>;
32
+ /** Result of reading the durable publisher registered for an App. */
33
+ export type TeamAppPublisherRegistrationRead = Readonly<{
34
+ kind: "found";
35
+ registration: TeamAppPublisherRegistration;
36
+ }> | Readonly<{
37
+ kind: "absent";
38
+ }>;
39
+ /** Idempotent result of registering one App publisher. */
40
+ export type TeamAppPublisherRegistrationCreate = Readonly<{
41
+ kind: "created" | "existing";
42
+ registration: TeamAppPublisherRegistration;
43
+ }> | Readonly<{
44
+ kind: "conflict";
45
+ }>;
46
+ /** Team-scoped decision for publishing a registered App. */
47
+ export type TeamAppPublicationAuthorization = Readonly<{
48
+ kind: "authorized";
49
+ registration: TeamAppPublisherRegistration;
50
+ }> | Readonly<{
51
+ kind: "absent" | "denied";
52
+ }>;
53
+ /** Durable App publisher facts; Team membership remains owned by Hub. */
54
+ export type TeamAppPublisherRegistry = Readonly<{
55
+ /** Read the publisher registration for an App. */
56
+ read(appId: AppId): Promise<TeamAppPublisherRegistrationRead>;
57
+ /** Atomically create or observe one immutable publisher registration. */
58
+ register(registration: TeamAppPublisherRegistration): Promise<TeamAppPublisherRegistrationCreate>;
59
+ /** Authorize a Team against the App's retained publisher registration. */
60
+ authorize(appId: AppId, teamId: string): Promise<TeamAppPublicationAuthorization>;
61
+ }>;
@@ -0,0 +1,16 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { appIdSchema, sourceRevisionSchema, } from "./internal/app-release-schema.js";
3
+ const boundedIdentity = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(256)));
4
+ /** Canonical source repository resolved by the product publisher. */
5
+ export const appSourceRepositorySchema = Schema.Struct({
6
+ provider: Schema.Literals(["artifacts", "github"]),
7
+ repository: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(1024))),
8
+ branch: Schema.Literal("main"),
9
+ });
10
+ /** Team-owned publication authority for one separately registered App. */
11
+ export const teamAppPublisherRegistrationSchema = Schema.Struct({
12
+ appId: appIdSchema,
13
+ teamId: boundedIdentity,
14
+ source: appSourceRepositorySchema,
15
+ templateSeed: Schema.optionalKey(Schema.Struct({ appId: appIdSchema, revision: sourceRevisionSchema })),
16
+ });