@builder.io/ai-utils 0.68.0 → 0.70.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@builder.io/ai-utils",
3
- "version": "0.68.0",
3
+ "version": "0.70.0",
4
4
  "description": "Builder.io AI utils",
5
5
  "files": [
6
6
  "src"
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Server-side view of the on-disk `builder.config.json` file, scoped to the
4
+ * fields the backend provisioning read path cares about (currently just
5
+ * `database`).
6
+ *
7
+ * This is intentionally a separate, parallel model from {@link FusionConfig}
8
+ * (codegen.ts), which is the dev-tools CLI's full view of the same file
9
+ * (parsed there as `Partial<FusionConfig>`). The two are different consumers:
10
+ * dev-tools owns launch/runtime config; this schema owns the server's
11
+ * database-provisioning read. `FusionConfig` has no `database` field, so this
12
+ * is additive rather than duplicative — keep new server-only config fields
13
+ * here and dev-tools/runtime fields on `FusionConfig`.
14
+ */
15
+ export declare const BuilderConfigDatabaseSchema: z.ZodObject<{
16
+ kind: z.ZodLiteral<"postgres">;
17
+ migrations: z.ZodLiteral<"drizzle">;
18
+ schemaDir: z.ZodDefault<z.ZodString>;
19
+ }, z.core.$strip>;
20
+ export type BuilderConfigDatabase = z.infer<typeof BuilderConfigDatabaseSchema>;
21
+ export declare const BuilderConfigSchema: z.ZodObject<{
22
+ database: z.ZodOptional<z.ZodObject<{
23
+ kind: z.ZodLiteral<"postgres">;
24
+ migrations: z.ZodLiteral<"drizzle">;
25
+ schemaDir: z.ZodDefault<z.ZodString>;
26
+ }, z.core.$strip>>;
27
+ }, z.core.$strip>;
28
+ export type BuilderConfig = z.infer<typeof BuilderConfigSchema>;
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Server-side view of the on-disk `builder.config.json` file, scoped to the
4
+ * fields the backend provisioning read path cares about (currently just
5
+ * `database`).
6
+ *
7
+ * This is intentionally a separate, parallel model from {@link FusionConfig}
8
+ * (codegen.ts), which is the dev-tools CLI's full view of the same file
9
+ * (parsed there as `Partial<FusionConfig>`). The two are different consumers:
10
+ * dev-tools owns launch/runtime config; this schema owns the server's
11
+ * database-provisioning read. `FusionConfig` has no `database` field, so this
12
+ * is additive rather than duplicative — keep new server-only config fields
13
+ * here and dev-tools/runtime fields on `FusionConfig`.
14
+ */
15
+ export const BuilderConfigDatabaseSchema = z.object({
16
+ kind: z.literal("postgres"),
17
+ migrations: z.literal("drizzle"),
18
+ schemaDir: z.string().default("drizzle"),
19
+ });
20
+ export const BuilderConfigSchema = z.object({
21
+ database: BuilderConfigDatabaseSchema.optional(),
22
+ });
package/src/codegen.d.ts CHANGED
@@ -486,6 +486,7 @@ export declare const TimelineEventCategorySchema: z.ZodEnum<{
486
486
  milestone: "milestone";
487
487
  navigation: "navigation";
488
488
  observation: "observation";
489
+ verified: "verified";
489
490
  }>;
490
491
  export type TimelineEventCategory = z.infer<typeof TimelineEventCategorySchema>;
491
492
  export declare const TestOutcomeSchema: z.ZodEnum<{
@@ -519,8 +520,10 @@ export declare const RecordFrameToolInputSchema: z.ZodObject<{
519
520
  milestone: "milestone";
520
521
  navigation: "navigation";
521
522
  observation: "observation";
523
+ verified: "verified";
522
524
  }>>;
523
525
  description: z.ZodOptional<z.ZodString>;
526
+ pr_highlight: z.ZodOptional<z.ZodBoolean>;
524
527
  }, z.core.$strip>;
525
528
  export type RecordFrameToolInput = z.infer<typeof RecordFrameToolInputSchema>;
526
529
  export declare const ReportTestOutcomeToolInputSchema: z.ZodObject<{
@@ -580,6 +583,8 @@ export interface TimelineFrameMetadata {
580
583
  displayDurationMs: number;
581
584
  fileName?: string;
582
585
  image_url?: string;
586
+ prHighlight?: boolean;
587
+ description?: string;
583
588
  cursorX?: number | null;
584
589
  cursorY?: number | null;
585
590
  viewportWidth?: number;
@@ -1587,8 +1592,10 @@ export declare const CodeGenToolMapSchema: z.ZodObject<{
1587
1592
  milestone: "milestone";
1588
1593
  navigation: "navigation";
1589
1594
  observation: "observation";
1595
+ verified: "verified";
1590
1596
  }>>;
1591
1597
  description: z.ZodOptional<z.ZodString>;
1598
+ pr_highlight: z.ZodOptional<z.ZodBoolean>;
1592
1599
  }, z.core.$strip>;
1593
1600
  SubmitPRReview: z.ZodObject<{
1594
1601
  summary: z.ZodString;
@@ -3993,7 +4000,7 @@ export interface FusionConfig {
3993
4000
  /** Whether this branch is for a code review - affects enabled agents and tools */
3994
4001
  branchType?: BranchType;
3995
4002
  /**
3996
- * When set, launch.ts hydrates `.builder/figma/<...>` from the corresponding
4003
+ * When set, launch.ts hydrates `.builder/sources/<...>` from the corresponding
3997
4004
  * GCS frame manifest before the agent starts. Only used on
3998
4005
  * `branchType === "design-system-indexing"` branches.
3999
4006
  */
package/src/codegen.js CHANGED
@@ -609,6 +609,7 @@ export const TimelineEventCategorySchema = z
609
609
  "milestone",
610
610
  "code-change",
611
611
  "observation",
612
+ "verified",
612
613
  ])
613
614
  .meta({ title: "TimelineEventCategory" });
614
615
  export const TestOutcomeSchema = z
@@ -642,6 +643,9 @@ export const RecordFrameToolInputSchema = z
642
643
  description: z.string().optional().meta({
643
644
  description: "Optional longer description providing context about what this frame shows.",
644
645
  }),
646
+ pr_highlight: z.boolean().optional().meta({
647
+ description: "Set true to mark this as a 'verified screenshot' worth showing in the PR description — i.e. visual proof of how the fix/feature behaves. Use sparingly (1–3 per session) for the key moments that demonstrate the feature working. The title is used as the caption.",
648
+ }),
645
649
  })
646
650
  .meta({ title: "RecordFrameToolInput" });
647
651
  export const ReportTestOutcomeToolInputSchema = z
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export interface FigmaHydrationFile {
3
- /** Relative path to write under `.builder/figma/`. */
3
+ /** Relative path to write under `.builder/sources/`. */
4
4
  localPath: string;
5
5
  downloadUrl: string;
6
6
  contentType: string;
@@ -24,7 +24,14 @@ export interface GenerateDesignSystemFormFields {
24
24
  * GUIDs are file-local, so they must be scoped per attachment.
25
25
  */
26
26
  selection?: Record<string, string[]>;
27
+ githubRepoUrl?: string;
27
28
  }
29
+ /**
30
+ * Accepts `https://github.com/<owner>/<repo>` (optionally with a trailing
31
+ * `.git` or path segments). Used to gate the public-repo input on the
32
+ * `/design-systems/v1/generate` endpoint.
33
+ */
34
+ export declare const GITHUB_REPO_URL_REGEX: RegExp;
28
35
  export interface GenerateDesignSystemRequest extends GenerateDesignSystemFormFields {
29
36
  /**
30
37
  * Mixed list of uploaded files in any order. Sent as repeated
@@ -41,6 +48,7 @@ export interface GenerateDesignSystemResponse {
41
48
  * resolved `branchName` / `branchUrl`.
42
49
  */
43
50
  jobId: string;
51
+ designSystemId: string;
44
52
  }
45
53
  export interface GenerateDesignSystemErrorResponse {
46
54
  error: string | Record<string, unknown>;
@@ -59,5 +67,6 @@ export declare const generateDesignSystemBodySchema: z.ZodObject<{
59
67
  projectName: z.ZodOptional<z.ZodString>;
60
68
  devToolsVersion: z.ZodOptional<z.ZodString>;
61
69
  selection: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
70
+ githubRepoUrl: z.ZodOptional<z.ZodString>;
62
71
  }, z.core.$strip>;
63
72
  export type GenerateDesignSystemBody = z.infer<typeof generateDesignSystemBodySchema>;
@@ -1,4 +1,10 @@
1
1
  import { z } from "zod";
2
+ /**
3
+ * Accepts `https://github.com/<owner>/<repo>` (optionally with a trailing
4
+ * `.git` or path segments). Used to gate the public-repo input on the
5
+ * `/design-systems/v1/generate` endpoint.
6
+ */
7
+ export const GITHUB_REPO_URL_REGEX = /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\.git)?\/?$/;
2
8
  export const GENERATE_DESIGN_SYSTEM_MAX_FILE_BYTES = 2 * 1024 * 1024 * 1024;
3
9
  export const GENERATE_DESIGN_SYSTEM_MAX_ATTACHMENTS = 50;
4
10
  export const GENERATE_DESIGN_SYSTEM_MIN_ATTACHMENTS = 1;
@@ -27,4 +33,9 @@ export const generateDesignSystemBodySchema = z.object({
27
33
  }
28
34
  }, z.record(z.string(), z.array(z.string().min(1)).max(10000)))
29
35
  .optional(),
36
+ githubRepoUrl: z
37
+ .string()
38
+ .trim()
39
+ .regex(GITHUB_REPO_URL_REGEX, "must be a public github.com repo URL")
40
+ .optional(),
30
41
  });
package/src/events.d.ts CHANGED
@@ -1052,7 +1052,21 @@ export type FigmaDecodeJobV1 = FusionEventVariant<"figma.decode.job", {
1052
1052
  mimetype: string;
1053
1053
  size: number;
1054
1054
  }>;
1055
+ /**
1056
+ * Optional public GitHub repo URL to include as supplementary context.
1057
+ * Recorded in the frame manifest; the container shallow-clones it into
1058
+ * `.builder/sources/<repo>/` at hydration time.
1059
+ */
1060
+ githubRepoUrl?: string;
1055
1061
  devToolsVersion?: string;
1062
+ /**
1063
+ * Optional map of `.fig` filename → page/frame GUID keys
1064
+ * (`"sessionID:localID"`) to restrict extraction to. When a filename is
1065
+ * present, only the listed pages/frames are decoded into the manifest;
1066
+ * filenames absent from the map are processed in full. GUIDs are
1067
+ * file-local, so they must be scoped per attachment.
1068
+ */
1069
+ selection?: Record<string, string[]>;
1056
1070
  }, {}, 1>;
1057
1071
  export declare const FigmaDecodeJobV1: {
1058
1072
  eventName: "figma.decode.job";
@@ -1086,9 +1100,25 @@ export interface FigmaFrameManifestEntry {
1086
1100
  export interface FigmaFrameAsset {
1087
1101
  /** Bucket-relative GCS path. */
1088
1102
  gcsPath: string;
1089
- /** Relative path under `.builder/figma/` (e.g. `images/abc123.png`). */
1103
+ /** Relative path under `.builder/sources/figma/` (e.g. `images/abc123.png`). */
1104
+ localPath: string;
1105
+ contentType: string;
1106
+ }
1107
+ /**
1108
+ * A plain-text / markdown attachment uploaded alongside the `.fig` files.
1109
+ */
1110
+ export interface FigmaTextSource {
1111
+ /** Relative path under `.builder/sources/` (e.g. `brand-guidelines.md`). */
1090
1112
  localPath: string;
1113
+ gcsPath: string;
1114
+ originalName: string;
1091
1115
  contentType: string;
1116
+ sizeBytes: number;
1117
+ }
1118
+ export interface FigmaGithubRepoSource {
1119
+ url: string;
1120
+ /** Directory name under `.builder/sources/` to clone into. */
1121
+ localDir: string;
1092
1122
  }
1093
1123
  export interface FigmaFrameManifest {
1094
1124
  jobId: string;
@@ -1096,6 +1126,10 @@ export interface FigmaFrameManifest {
1096
1126
  frames: FigmaFrameManifestEntry[];
1097
1127
  /** Raster assets referenced by frame HTML via relative `images/…` URLs. */
1098
1128
  assets?: FigmaFrameAsset[];
1129
+ /** Uploaded plain-text / markdown supplementary sources. */
1130
+ textSources?: FigmaTextSource[];
1131
+ /** Optional public GitHub repo to clone as supplementary context. */
1132
+ githubRepo?: FigmaGithubRepoSource;
1099
1133
  }
1100
1134
  export type PrReviewRequestedV1 = FusionEventVariant<"pr.review.requested", {
1101
1135
  repoFullName: string;
package/src/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./builder-config.js";
1
2
  export * from "./completion.js";
2
3
  export * from "./events.js";
3
4
  export * from "./messages.js";
package/src/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./builder-config.js";
1
2
  export * from "./completion.js";
2
3
  export * from "./events.js";
3
4
  export * from "./messages.js";
@@ -70,6 +70,7 @@ interface OrganizationSettings {
70
70
  enableModelValidationHooks?: boolean;
71
71
  enableOrgInsights?: boolean;
72
72
  enableBuilderHosting?: boolean;
73
+ enableFusionHosting?: boolean;
73
74
  allowLegacyPlanSubscriptions?: boolean;
74
75
  enforceMaxUsers?: boolean;
75
76
  ssoRestrictedMode?: boolean;
package/src/projects.d.ts CHANGED
@@ -435,7 +435,7 @@ export interface OrgAgentConfig {
435
435
  enabledTools: string[];
436
436
  longLived: boolean;
437
437
  }
438
- export type BranchType = "code-review" | "setup-project" | "org-agent" | "snapshot-build" | "design-system-indexing" | "default";
438
+ export type BranchType = "code-review" | "setup-project" | "org-agent" | "snapshot-build" | "design-system-indexing" | "deploy" | "default";
439
439
  /** Category of work a branch represents, auto-assigned during prompt analysis. */
440
440
  export type BranchCategory = "feature" | "fix" | "research" | "other";
441
441
  export interface BranchSharedData {
@@ -526,6 +526,7 @@ export interface BranchSharedData {
526
526
  lastReviewId?: string | null;
527
527
  /** Timestamp of the last PR review */
528
528
  lastReviewAt?: number | null;
529
+ database?: BranchDatabase;
529
530
  }
530
531
  /**
531
532
  * fields that are required in the new branch format, but optional in the legacy branch format.
@@ -864,9 +865,55 @@ export interface Project {
864
865
  userInitiatedAutoSetup?: boolean;
865
866
  /** When true, automatically apply verified setup configuration when the setup agent completes */
866
867
  autoApplySetup?: boolean;
867
- /** Settings related to Fusion fist class hosting */
868
+ /** Settings related to Fusion first class hosting */
868
869
  hosting?: ProjectHosting;
870
+ database?: ProjectDatabase;
869
871
  }
872
+ export interface ProjectDatabase {
873
+ ownerId: string;
874
+ neonOrgId: string;
875
+ neonOrgTier: "free" | "paid";
876
+ neonProjectId: string;
877
+ mainBranchId: string;
878
+ mainEndpointHost: string;
879
+ mainEndpointPoolerHost: string;
880
+ role: string;
881
+ /** @internal Plaintext credential — Firestore IAM enforces access. Never serialize to client responses. */
882
+ password: string;
883
+ /** Database name (white-labeled to "main"); needed to build connection URIs. */
884
+ database: string;
885
+ region: string;
886
+ createdAt: number;
887
+ createdBy: string;
888
+ lastActivityAt: number;
889
+ }
890
+ export interface BranchDatabase {
891
+ ownerId: string;
892
+ projectId: string;
893
+ neonProjectId: string;
894
+ neonBranchId: string;
895
+ endpointHost: string;
896
+ endpointPoolerHost: string;
897
+ createdAt: number;
898
+ createdBy: string;
899
+ }
900
+ interface DatabaseDeletionBase {
901
+ id: string;
902
+ ownerId: string;
903
+ projectId: string;
904
+ neonProjectId: string;
905
+ pendingDeleteAt: number;
906
+ createdAt: number;
907
+ createdBy: string;
908
+ reason?: string;
909
+ }
910
+ export type DatabaseDeletion = (DatabaseDeletionBase & {
911
+ neonBranchId: string;
912
+ neonProjectAllBranches?: never;
913
+ }) | (DatabaseDeletionBase & {
914
+ neonBranchId?: never;
915
+ neonProjectAllBranches: true;
916
+ });
870
917
  /**
871
918
  * Get the state of a branch, checking `state` first and falling back to `deleted` for backwards compatibility.
872
919
  */
@@ -1221,9 +1268,13 @@ export interface ShouldDeletePodResponse {
1221
1268
  * - deploying — queue handler is pushing artifacts to Netlify + updating Envoy HTTPRoute
1222
1269
  * - live — terminal: site is serving traffic
1223
1270
  * - failed — terminal: see `error` for the one-line reason
1271
+ * - canceled — terminal: a concurrent unpublish (or an expired-lock takeover by a
1272
+ * newer deploy) stole the lock, and this deploy aborted at a fencing
1273
+ * gate before going live — see "Concurrency & Locking" in the spec
1224
1274
  */
1225
1275
  export declare const DeployStatusSchema: z.ZodEnum<{
1226
1276
  building: "building";
1277
+ canceled: "canceled";
1227
1278
  deploying: "deploying";
1228
1279
  failed: "failed";
1229
1280
  live: "live";
@@ -1249,17 +1300,27 @@ export declare const ProjectHostingSchema: z.ZodObject<{
1249
1300
  buildCommand: z.ZodOptional<z.ZodString>;
1250
1301
  nodeVersion: z.ZodOptional<z.ZodString>;
1251
1302
  }, z.core.$strip>>;
1252
- environment: z.ZodOptional<z.ZodArray<z.ZodObject<{
1253
- key: z.ZodString;
1254
- value: z.ZodString;
1255
- isSecret: z.ZodBoolean;
1256
- placeholder: z.ZodOptional<z.ZodBoolean>;
1257
- explanation: z.ZodOptional<z.ZodString>;
1258
- }, z.core.$strip>>>;
1303
+ environment: z.ZodOptional<z.ZodObject<{
1304
+ build: z.ZodOptional<z.ZodArray<z.ZodObject<{
1305
+ key: z.ZodString;
1306
+ value: z.ZodString;
1307
+ isSecret: z.ZodBoolean;
1308
+ placeholder: z.ZodOptional<z.ZodBoolean>;
1309
+ explanation: z.ZodOptional<z.ZodString>;
1310
+ }, z.core.$strip>>>;
1311
+ prod: z.ZodOptional<z.ZodArray<z.ZodObject<{
1312
+ key: z.ZodString;
1313
+ value: z.ZodString;
1314
+ isSecret: z.ZodBoolean;
1315
+ placeholder: z.ZodOptional<z.ZodBoolean>;
1316
+ explanation: z.ZodOptional<z.ZodString>;
1317
+ }, z.core.$strip>>>;
1318
+ }, z.core.$strip>>;
1259
1319
  lastDeployId: z.ZodOptional<z.ZodString>;
1260
1320
  lastDeployAt: z.ZodOptional<z.ZodNumber>;
1261
1321
  lastDeployStatus: z.ZodOptional<z.ZodEnum<{
1262
1322
  building: "building";
1323
+ canceled: "canceled";
1263
1324
  deploying: "deploying";
1264
1325
  failed: "failed";
1265
1326
  live: "live";
@@ -1280,8 +1341,10 @@ export declare const DeploySchema: z.ZodObject<{
1280
1341
  ownerId: z.ZodString;
1281
1342
  checkoutBranch: z.ZodOptional<z.ZodString>;
1282
1343
  deployBranchId: z.ZodOptional<z.ZodString>;
1344
+ devBranchId: z.ZodOptional<z.ZodString>;
1283
1345
  status: z.ZodEnum<{
1284
1346
  building: "building";
1347
+ canceled: "canceled";
1285
1348
  deploying: "deploying";
1286
1349
  failed: "failed";
1287
1350
  live: "live";
@@ -1314,4 +1377,60 @@ export declare const HostingSlugSchema: z.ZodObject<{
1314
1377
  createdBy: z.ZodString;
1315
1378
  }, z.core.$strip>;
1316
1379
  export type HostingSlug = z.infer<typeof HostingSlugSchema>;
1380
+ /**
1381
+ * Deploy lease lock with fencing token, stored at
1382
+ * `hosting-locks/{projectId}__{checkoutBranch}` (the key uses the resolved ref
1383
+ * string, not a branchId, because `main` has no backing fusion-branch doc in PRs
1384
+ * mode).
1385
+ *
1386
+ * Serializes deploys per `(projectId, checkoutBranch)` and lets unpublish preempt an
1387
+ * in-flight deploy. Created by the `POST /projects/deploy` acquire transaction,
1388
+ * force-stolen by `DELETE /projects/hosting`, and deleted on any terminal deploy
1389
+ * state (`live` / `failed` / `canceled`) or after unpublish teardown. A crashed
1390
+ * holder's lock is reclaimed lazily via `expiresAt` rather than auto-released.
1391
+ */
1392
+ export declare const HostingLockSchema: z.ZodObject<{
1393
+ ownerId: z.ZodString;
1394
+ holderId: z.ZodString;
1395
+ holderKind: z.ZodEnum<{
1396
+ deploy: "deploy";
1397
+ unpublish: "unpublish";
1398
+ }>;
1399
+ deployId: z.ZodOptional<z.ZodString>;
1400
+ acquiredAt: z.ZodNumber;
1401
+ expiresAt: z.ZodNumber;
1402
+ }, z.core.$strip>;
1403
+ export type HostingLock = z.infer<typeof HostingLockSchema>;
1404
+ /**
1405
+ * Response item for `GET /projects/deploys`. The same shape backs both the deploy
1406
+ * history list and the single-deploy poll, so the frontend can render both with one
1407
+ * renderer.
1408
+ */
1409
+ export declare const DeployListItemSchema: z.ZodObject<{
1410
+ id: z.ZodString;
1411
+ status: z.ZodEnum<{
1412
+ building: "building";
1413
+ canceled: "canceled";
1414
+ deploying: "deploying";
1415
+ failed: "failed";
1416
+ live: "live";
1417
+ queued: "queued";
1418
+ uploading: "uploading";
1419
+ }>;
1420
+ url: z.ZodOptional<z.ZodString>;
1421
+ error: z.ZodOptional<z.ZodString>;
1422
+ createdAt: z.ZodNumber;
1423
+ completedAt: z.ZodOptional<z.ZodNumber>;
1424
+ }, z.core.$strip>;
1425
+ export type DeployListItem = z.infer<typeof DeployListItemSchema>;
1426
+ /**
1427
+ * Shape of the `manifest.json` the deploy pod uploads to GCS alongside
1428
+ * `artifact.tar.gz`. Modeled on Netlify's deploy file map: static files are hashed
1429
+ * with sha1, serverless functions with sha256 (per Netlify's contract).
1430
+ */
1431
+ export declare const DeployArtifactManifestSchema: z.ZodObject<{
1432
+ files: z.ZodRecord<z.ZodString, z.ZodString>;
1433
+ functions: z.ZodRecord<z.ZodString, z.ZodString>;
1434
+ }, z.core.$strip>;
1435
+ export type DeployArtifactManifest = z.infer<typeof DeployArtifactManifestSchema>;
1317
1436
  export {};
package/src/projects.js CHANGED
@@ -180,6 +180,9 @@ export function parseExitPlanMode(chunk) {
180
180
  * - deploying — queue handler is pushing artifacts to Netlify + updating Envoy HTTPRoute
181
181
  * - live — terminal: site is serving traffic
182
182
  * - failed — terminal: see `error` for the one-line reason
183
+ * - canceled — terminal: a concurrent unpublish (or an expired-lock takeover by a
184
+ * newer deploy) stole the lock, and this deploy aborted at a fencing
185
+ * gate before going live — see "Concurrency & Locking" in the spec
183
186
  */
184
187
  export const DeployStatusSchema = z.enum([
185
188
  "queued",
@@ -188,6 +191,7 @@ export const DeployStatusSchema = z.enum([
188
191
  "deploying",
189
192
  "live",
190
193
  "failed",
194
+ "canceled",
191
195
  ]);
192
196
  export const ProjectHostingBuildConfigSchema = z.object({
193
197
  buildCommand: z.string().optional(),
@@ -222,9 +226,16 @@ export const ProjectHostingSchema = z.object({
222
226
  * plaintext in Firestore, masked on the read path before responses leave ai-services
223
227
  * (see "Secret handling" in the hosting spec).
224
228
  */
225
- environment: z.array(EnvironmentVariableSchema).optional().meta({
226
- description: "Production environment variables set when we deploy",
227
- }),
229
+ environment: z
230
+ .object({
231
+ build: z.array(EnvironmentVariableSchema).optional().meta({
232
+ description: "Env vars available to the build itself",
233
+ }),
234
+ prod: z.array(EnvironmentVariableSchema).optional().meta({
235
+ description: "Env vars for the production deploy served via Netlify",
236
+ }),
237
+ })
238
+ .optional(),
228
239
  lastDeployId: z.string().optional(),
229
240
  lastDeployAt: z.number().optional(),
230
241
  lastDeployStatus: DeployStatusSchema.optional(),
@@ -250,13 +261,11 @@ export const DeploySchema = z.object({
250
261
  "for row 2 in v2). This is the value passed as `checkoutBranch` when the " +
251
262
  "deploy pod was created.",
252
263
  }),
253
- deployBranchId: z
254
- .string()
255
- .optional()
256
- .meta({
257
- description: 'ID of the ephemeral hidden deploy branch (`type: "deploy"`) that ran the ' +
258
- "build. Cleared after the pod terminates so historical Deploy docs don't " +
259
- "hold references to garbage-collected branch state.",
264
+ deployBranchId: z.string().optional().meta({
265
+ description: 'Fusion branch id of the ephemeral hidden deploy branch (`type: "deploy"`) that ran the build.',
266
+ }),
267
+ devBranchId: z.string().optional().meta({
268
+ description: "Fusion dev branch's id (unset if we are deploying project default branch)",
260
269
  }),
261
270
  status: DeployStatusSchema,
262
271
  netlifyDeployId: z.string().optional(),
@@ -289,3 +298,55 @@ export const HostingSlugSchema = z.object({
289
298
  description: "user ID of the user who first clicked Publish on the project",
290
299
  }),
291
300
  });
301
+ /**
302
+ * Deploy lease lock with fencing token, stored at
303
+ * `hosting-locks/{projectId}__{checkoutBranch}` (the key uses the resolved ref
304
+ * string, not a branchId, because `main` has no backing fusion-branch doc in PRs
305
+ * mode).
306
+ *
307
+ * Serializes deploys per `(projectId, checkoutBranch)` and lets unpublish preempt an
308
+ * in-flight deploy. Created by the `POST /projects/deploy` acquire transaction,
309
+ * force-stolen by `DELETE /projects/hosting`, and deleted on any terminal deploy
310
+ * state (`live` / `failed` / `canceled`) or after unpublish teardown. A crashed
311
+ * holder's lock is reclaimed lazily via `expiresAt` rather than auto-released.
312
+ */
313
+ export const HostingLockSchema = z.object({
314
+ ownerId: z.string(),
315
+ holderId: z.string().meta({
316
+ description: "unique per acquisition (the fencing token); == deployId for a deploy holder",
317
+ }),
318
+ holderKind: z.enum(["deploy", "unpublish"]),
319
+ deployId: z.string().optional().meta({
320
+ description: 'set when holderKind === "deploy"',
321
+ }),
322
+ acquiredAt: z.number().meta({ description: "unix time (ms)" }),
323
+ expiresAt: z.number().meta({
324
+ description: "acquiredAt + 15min (same ceiling as the deploy lazy-timeout)",
325
+ }),
326
+ });
327
+ /**
328
+ * Response item for `GET /projects/deploys`. The same shape backs both the deploy
329
+ * history list and the single-deploy poll, so the frontend can render both with one
330
+ * renderer.
331
+ */
332
+ export const DeployListItemSchema = DeploySchema.pick({
333
+ id: true,
334
+ status: true,
335
+ url: true,
336
+ createdAt: true,
337
+ completedAt: true,
338
+ error: true,
339
+ }).meta({ title: "DeployListItem" });
340
+ /**
341
+ * Shape of the `manifest.json` the deploy pod uploads to GCS alongside
342
+ * `artifact.tar.gz`. Modeled on Netlify's deploy file map: static files are hashed
343
+ * with sha1, serverless functions with sha256 (per Netlify's contract).
344
+ */
345
+ export const DeployArtifactManifestSchema = z.object({
346
+ files: z.record(z.string(), z.string()).meta({
347
+ description: "static file path → sha1 hash",
348
+ }),
349
+ functions: z.record(z.string(), z.string()).meta({
350
+ description: "serverless function name → sha256 hash",
351
+ }),
352
+ });