@plaintake/scenario 1.17.0 → 1.24.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/dist/index.js CHANGED
@@ -1,3 +1,62 @@
1
+ // ../schema/src/baseline.ts
2
+ import { z } from "zod";
3
+ var Sha256 = z.string().regex(/^[0-9a-f]{64}$/);
4
+ var DiffCategorySchema = z.enum([
5
+ "step",
6
+ "assertion",
7
+ "target-name",
8
+ "target-role",
9
+ "target-bounds",
10
+ "timing",
11
+ "caption",
12
+ "actor"
13
+ ]);
14
+ var SeveritySchema = z.enum(["fail", "report"]);
15
+ var DifferenceSchema = z.object({
16
+ category: DiffCategorySchema,
17
+ severity: SeveritySchema,
18
+ detail: z.string()
19
+ });
20
+ var RectSchema = z.object({ x: z.number(), y: z.number(), width: z.number(), height: z.number() });
21
+ var SnapshotTargetSchema = z.object({
22
+ role: z.string().nullable(),
23
+ accessibleName: z.string().nullable(),
24
+ rect: RectSchema.nullable()
25
+ });
26
+ var SnapshotStepSchema = z.object({
27
+ stableId: z.string().min(1),
28
+ target: SnapshotTargetSchema.nullable(),
29
+ /** `null` for a step that started and never finished. */
30
+ durationMs: z.number().nullable()
31
+ });
32
+ var SemanticSnapshotSchema = z.object({
33
+ /** sha256 of the bundle's `scenario/scenario.ts`; `null` when the bundle has none. */
34
+ scenarioSha256: Sha256.nullable(),
35
+ steps: z.array(SnapshotStepSchema),
36
+ assertions: z.array(z.object({ stableId: z.string().min(1), status: z.enum(["passed", "failed"]) })),
37
+ /** Absent when the bundle carries no render plan — a `check` bundle. */
38
+ captions: z.array(z.object({ id: z.string(), lines: z.array(z.string()) })).optional(),
39
+ actors: z.object({
40
+ ids: z.array(z.string()),
41
+ turnOrder: z.array(z.string()),
42
+ /** Absent without a render plan, or for a single-actor plan. */
43
+ labels: z.array(z.object({ id: z.string(), label: z.string() })).optional()
44
+ })
45
+ });
46
+ var BASELINE_SCHEMA = "plaintake.baseline/v1";
47
+ var BaselineFileSchema = z.object({
48
+ schema: z.literal(BASELINE_SCHEMA),
49
+ /** Informational only: which PlainTake wrote the file. */
50
+ plaintakeVersion: z.string().min(1),
51
+ snapshot: SemanticSnapshotSchema.extend({ scenarioSha256: Sha256 })
52
+ });
53
+ var BaselineStatusSchema = z.enum(["match", "drift", "scenario-changed", "absent", "skipped", "updated"]);
54
+ var BaselineReportSchema = z.object({
55
+ path: z.string().min(1),
56
+ status: BaselineStatusSchema,
57
+ differences: z.array(DifferenceSchema)
58
+ });
59
+
1
60
  // ../schema/src/errors.ts
2
61
  var EXIT_CODES = {
3
62
  ok: 0,
@@ -6,7 +65,8 @@ var EXIT_CODES = {
6
65
  toolchain: 3,
7
66
  capture: 4,
8
67
  render: 5,
9
- verification: 6
68
+ verification: 6,
69
+ drift: 7
10
70
  };
11
71
  var DemoError = class extends Error {
12
72
  kind;
@@ -22,9 +82,9 @@ var DemoError = class extends Error {
22
82
  };
23
83
 
24
84
  // ../schema/src/event.ts
25
- import { z } from "zod";
26
- var NanosString = z.string().regex(/^\d+$/, "must be a digit-only nanosecond string");
27
- var DemoEventTypeSchema = z.enum([
85
+ import { z as z2 } from "zod";
86
+ var NanosString = z2.string().regex(/^\d+$/, "must be a digit-only nanosecond string");
87
+ var DemoEventTypeSchema = z2.enum([
28
88
  "session.start",
29
89
  "session.finish",
30
90
  "chapter",
@@ -64,23 +124,23 @@ var DemoEventTypeSchema = z.enum([
64
124
  */
65
125
  "explain"
66
126
  ]);
67
- var TargetSchema = z.object({
68
- role: z.string().optional(),
69
- accessibleName: z.string().optional(),
70
- selector: z.string().optional(),
71
- rect: z.object({ x: z.number(), y: z.number(), width: z.number(), height: z.number() }).optional()
127
+ var TargetSchema = z2.object({
128
+ role: z2.string().optional(),
129
+ accessibleName: z2.string().optional(),
130
+ selector: z2.string().optional(),
131
+ rect: z2.object({ x: z2.number(), y: z2.number(), width: z2.number(), height: z2.number() }).optional()
72
132
  });
73
- var DemoEventSchema = z.object({
74
- schema: z.literal("agent-demo.event/v1"),
75
- seq: z.number().int().nonnegative(),
133
+ var DemoEventSchema = z2.object({
134
+ schema: z2.literal("agent-demo.event/v1"),
135
+ seq: z2.number().int().nonnegative(),
76
136
  tStartNs: NanosString,
77
137
  tEndNs: NanosString.optional(),
78
138
  type: DemoEventTypeSchema,
79
- stableId: z.string().min(1).optional(),
80
- title: z.string().optional(),
81
- subtitle: z.string().optional(),
139
+ stableId: z2.string().min(1).optional(),
140
+ title: z2.string().optional(),
141
+ subtitle: z2.string().optional(),
82
142
  target: TargetSchema.optional(),
83
- payload: z.record(z.string(), z.unknown()).optional(),
143
+ payload: z2.record(z2.string(), z2.unknown()).optional(),
84
144
  /**
85
145
  * Which actor performed this event, for a `turn` hand-off or a verb step taken during one.
86
146
  * Absent means the scenario's default actor — a single-actor event log never sets this, so
@@ -88,27 +148,27 @@ var DemoEventSchema = z.object({
88
148
  * here: the default actor's id is an application-layer notion the recorder resolves, not a
89
149
  * value this schema should invent and materialise on parse.
90
150
  */
91
- actor: z.string().min(1).optional()
151
+ actor: z2.string().min(1).optional()
92
152
  });
93
153
 
94
154
  // ../schema/src/json-schema.ts
95
- import { z as z3 } from "zod";
155
+ import { z as z4 } from "zod";
96
156
 
97
157
  // ../schema/src/scenario.ts
98
- import { z as z2 } from "zod";
99
- var ScenarioIntroSchema = z2.object({
100
- lines: z2.array(z2.string().min(1)).min(1).max(2, "intro.lines takes at most two lines \u2014 a third would have to shrink the type to fit"),
101
- narration: z2.string().min(1).optional(),
102
- durationMs: z2.number().int().positive().max(15e3, "intro.durationMs must be at most 15000ms").optional()
158
+ import { z as z3 } from "zod";
159
+ var ScenarioIntroSchema = z3.object({
160
+ lines: z3.array(z3.string().min(1)).min(1).max(2, "intro.lines takes at most two lines \u2014 a third would have to shrink the type to fit"),
161
+ narration: z3.string().min(1).optional(),
162
+ durationMs: z3.number().int().positive().max(15e3, "intro.durationMs must be at most 15000ms").optional()
103
163
  });
104
- var ScenarioCameraSchema = z2.object({
105
- maxZoom: z2.number().min(1, "camera.maxZoom must be at least 1 (no zoom)").max(1.6, "camera.maxZoom must be at most 1.6 \u2014 beyond that the upscaled capture turns UI text to mush").optional(),
106
- margin: z2.number().min(0, "camera.margin must be at least 0").max(2, "camera.margin must be at most 2 (as a fraction of the target, per side)").optional(),
107
- easeMs: z2.number().int().min(133, "camera.easeMs must be at least 133ms").max(2e3, "camera.easeMs must be at most 2000ms").optional(),
108
- minDwellMs: z2.number().int().min(0, "camera.minDwellMs must be at least 0").max(5e3, "camera.minDwellMs must be at most 5000ms").optional()
164
+ var ScenarioCameraSchema = z3.object({
165
+ maxZoom: z3.number().min(1, "camera.maxZoom must be at least 1 (no zoom)").max(1.6, "camera.maxZoom must be at most 1.6 \u2014 beyond that the upscaled capture turns UI text to mush").optional(),
166
+ margin: z3.number().min(0, "camera.margin must be at least 0").max(2, "camera.margin must be at most 2 (as a fraction of the target, per side)").optional(),
167
+ easeMs: z3.number().int().min(133, "camera.easeMs must be at least 133ms").max(2e3, "camera.easeMs must be at most 2000ms").optional(),
168
+ minDwellMs: z3.number().int().min(0, "camera.minDwellMs must be at least 0").max(5e3, "camera.minDwellMs must be at most 5000ms").optional()
109
169
  });
110
- var ScenarioSpeechSchema = z2.object({
111
- speed: z2.number().min(0.5, "speech.speed must be at least 0.5 (half speed)").max(2, "speech.speed must be at most 2.0 (double speed) \u2014 an unmeasured, conservative ceiling").optional(),
170
+ var ScenarioSpeechSchema = z3.object({
171
+ speed: z3.number().min(0.5, "speech.speed must be at least 0.5 (half speed)").max(2, "speech.speed must be at most 2.0 (double speed) \u2014 an unmeasured, conservative ceiling").optional(),
112
172
  /*
113
173
  * The voices this scenario may switch to mid-run, beyond the run-wide default `--voice`
114
174
  * picks. A step names one with `demo.step({ voice })` or an actor with `demo.actor(id,
@@ -135,24 +195,24 @@ var ScenarioSpeechSchema = z2.object({
135
195
  * A mix is refused at record time with both sides named — the run's pronunciation
136
196
  * dictionary is configured once, for the one language it speaks.
137
197
  */
138
- voices: z2.array(z2.string().min(1, "speech.voices entries must be non-empty voice names")).min(1, "speech.voices must name at least one voice \u2014 an empty list declares nothing, so omit it").optional()
198
+ voices: z3.array(z3.string().min(1, "speech.voices entries must be non-empty voice names")).min(1, "speech.voices must name at least one voice \u2014 an empty list declares nothing, so omit it").optional()
139
199
  });
140
- var ScenarioMetaSchema = z2.object({
141
- schema: z2.literal("agent-demo.scenario/v1"),
142
- id: z2.string().regex(/^[a-z0-9][a-z0-9-]*$/, "must be a lowercase kebab-case id"),
143
- title: z2.string().min(1),
144
- language: z2.string().min(2).default("en"),
145
- viewport: z2.object({
146
- width: z2.literal(1920),
147
- height: z2.literal(1080),
148
- deviceScaleFactor: z2.literal(1)
200
+ var ScenarioMetaSchema = z3.object({
201
+ schema: z3.literal("agent-demo.scenario/v1"),
202
+ id: z3.string().regex(/^[a-z0-9][a-z0-9-]*$/, "must be a lowercase kebab-case id"),
203
+ title: z3.string().min(1),
204
+ language: z3.string().min(2).default("en"),
205
+ viewport: z3.object({
206
+ width: z3.literal(1920),
207
+ height: z3.literal(1080),
208
+ deviceScaleFactor: z3.literal(1)
149
209
  }),
150
- locale: z2.literal("en-US"),
151
- timezoneId: z2.literal("UTC"),
152
- colorScheme: z2.literal("light"),
153
- reducedMotion: z2.literal("reduce"),
210
+ locale: z3.literal("en-US"),
211
+ timezoneId: z3.literal("UTC"),
212
+ colorScheme: z3.literal("light"),
213
+ reducedMotion: z3.literal("reduce"),
154
214
  /** Exact console error strings the scenario tolerates. Anything else fails the run. */
155
- allowedConsoleErrors: z2.array(z2.string()).default([]),
215
+ allowedConsoleErrors: z3.array(z3.string()).default([]),
156
216
  /*
157
217
  * Whether this scenario hands the real browser window to a person, and in which phase.
158
218
  *
@@ -170,7 +230,7 @@ var ScenarioMetaSchema = z2.object({
170
230
  * The same precedent as everywhere else here: a `.default()`, so every scenario written
171
231
  * before this parses unchanged.
172
232
  */
173
- handoff: z2.enum(["none", "preflight", "session"]).default("none"),
233
+ handoff: z3.enum(["none", "preflight", "session"]).default("none"),
174
234
  /*
175
235
  * How long a handoff waits for the person before giving up. Not a patience limit —
176
236
  * past a few minutes, a block is indistinguishable from a hang, and the person is better
@@ -179,7 +239,7 @@ var ScenarioMetaSchema = z2.object({
179
239
  * In the schema rather than only in the runtime so `validate` catches an out-of-range
180
240
  * value with no browser and no waiting.
181
241
  */
182
- handoffTimeoutMs: z2.number().int().min(5e3, "handoffTimeoutMs must be at least 5000ms").max(
242
+ handoffTimeoutMs: z3.number().int().min(5e3, "handoffTimeoutMs must be at least 5000ms").max(
183
243
  3e5,
184
244
  "handoffTimeoutMs must be at most 300000ms (5 minutes) \u2014 past that a wait is indistinguishable from a hang"
185
245
  ).default(12e4),
@@ -227,120 +287,120 @@ var ScenarioMetaSchema = z2.object({
227
287
  *
228
288
  * Optional and inert when absent, the same precedent as everything else on this schema.
229
289
  */
230
- pronunciations: z2.record(z2.string().min(1, "pronunciation keys must not be empty"), z2.string()).optional()
290
+ pronunciations: z3.record(z3.string().min(1, "pronunciation keys must not be empty"), z3.string()).optional()
231
291
  });
232
292
 
233
293
  // ../schema/src/manifest.ts
234
- import { z as z4 } from "zod";
235
- var Sha256 = z4.string().regex(/^[0-9a-f]{64}$/);
236
- var BundleManifestSchema = z4.object({
237
- schema: z4.literal("agent-demo.bundle/v1"),
238
- status: z4.enum(["passed", "failed"]),
239
- scenario: z4.object({ id: z4.string().min(1), sourceSha256: Sha256 }),
240
- toolchain: z4.object({
241
- node: z4.string(),
242
- playwright: z4.string(),
243
- chromiumRevision: z4.string(),
244
- ffmpeg: z4.string(),
294
+ import { z as z5 } from "zod";
295
+ var Sha2562 = z5.string().regex(/^[0-9a-f]{64}$/);
296
+ var BundleManifestSchema = z5.object({
297
+ schema: z5.literal("agent-demo.bundle/v1"),
298
+ status: z5.enum(["passed", "failed"]),
299
+ scenario: z5.object({ id: z5.string().min(1), sourceSha256: Sha2562 }),
300
+ toolchain: z5.object({
301
+ node: z5.string(),
302
+ playwright: z5.string(),
303
+ chromiumRevision: z5.string(),
304
+ ffmpeg: z5.string(),
245
305
  /** May be the literal "unknown" — never a fabricated version. */
246
- libass: z4.string(),
247
- fontSha256: Sha256,
248
- containerImage: z4.string().optional(),
306
+ libass: z5.string(),
307
+ fontSha256: Sha2562,
308
+ containerImage: z5.string().optional(),
249
309
  /**
250
310
  * The plainmotion CLI's own `--version` string, present exactly when a scenario called
251
311
  * `demo.explain()` at least once — a run that never cut away never spawned it, and
252
312
  * `containerImage`'s own optionality is the precedent for saying so with an absent field
253
313
  * rather than a fabricated one.
254
314
  */
255
- plainmotion: z4.string().optional()
315
+ plainmotion: z5.string().optional()
256
316
  }),
257
- environment: z4.object({
258
- os: z4.string(),
317
+ environment: z5.object({
318
+ os: z5.string(),
259
319
  /** Part of the reproducibility claim: byte-identity holds per architecture. */
260
- architecture: z4.string(),
261
- locale: z4.literal("en-US"),
262
- timezone: z4.literal("UTC"),
263
- viewport: z4.tuple([z4.literal(1920), z4.literal(1080)]),
264
- deviceScaleFactor: z4.literal(1)
320
+ architecture: z5.string(),
321
+ locale: z5.literal("en-US"),
322
+ timezone: z5.literal("UTC"),
323
+ viewport: z5.tuple([z5.literal(1920), z5.literal(1080)]),
324
+ deviceScaleFactor: z5.literal(1)
265
325
  }),
266
- assertions: z4.array(z4.object({ id: z4.string().min(1), status: z4.enum(["passed", "failed"]) })),
267
- artifacts: z4.array(
268
- z4.object({
269
- path: z4.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/, "must be a relative POSIX path"),
270
- sha256: Sha256,
271
- bytes: z4.number().int().nonnegative(),
272
- role: z4.enum(["source", "derived"])
326
+ assertions: z5.array(z5.object({ id: z5.string().min(1), status: z5.enum(["passed", "failed"]) })),
327
+ artifacts: z5.array(
328
+ z5.object({
329
+ path: z5.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/, "must be a relative POSIX path"),
330
+ sha256: Sha2562,
331
+ bytes: z5.number().int().nonnegative(),
332
+ role: z5.enum(["source", "derived"])
273
333
  })
274
334
  )
275
335
  });
276
336
 
277
337
  // ../schema/src/narration-index.ts
278
- import { z as z5 } from "zod";
338
+ import { z as z6 } from "zod";
279
339
  var NARRATION_INDEX_SCHEMA = "agent-demo.narration-index/v1";
280
- var NarrationIndexClipSchema = z5.object({
340
+ var NarrationIndexClipSchema = z6.object({
281
341
  /** The step id, which is also the clip id and the cache filename stem. */
282
- id: z5.string().min(1),
342
+ id: z6.string().min(1),
283
343
  /**
284
344
  * The marker-free, normalised line — `clipFor`'s `text`. Never empty: the runtime does not call
285
345
  * the narrator for a step that normalises to nothing, so an empty line here is a corrupt index.
286
346
  */
287
- text: z5.string().min(1),
347
+ text: z6.string().min(1),
288
348
  /**
289
349
  * The author's `[pause]` segments, present only when a marker actually split the line — which is
290
350
  * two or more pieces, never one. When present it is what the cache key is built from (the pieces
291
351
  * join by NUL), so it must round-trip exactly or a warmed marked line lands under a different key
292
352
  * from the one the recording will look for.
293
353
  */
294
- breaks: z5.array(z5.string().min(1)).min(2).optional(),
354
+ breaks: z6.array(z6.string().min(1)).min(2).optional(),
295
355
  /**
296
356
  * The resolved voice, present only when it was not the run's default (`speech.voice` below).
297
357
  * Absent means "spoken by the default", exactly as it does on the frozen plan's per-clip voice.
298
358
  */
299
- voice: z5.string().min(1).optional()
359
+ voice: z6.string().min(1).optional()
300
360
  });
301
- var NarrationIndexSchema = z5.object({
302
- schema: z5.literal(NARRATION_INDEX_SCHEMA),
303
- speech: z5.object({
304
- voice: z5.string().min(1),
305
- voices: z5.array(z5.string().min(1)).optional(),
306
- speed: z5.number().positive(),
307
- dtype: z5.string().min(1),
308
- pronunciations: z5.record(z5.string().min(1), z5.string()).optional()
361
+ var NarrationIndexSchema = z6.object({
362
+ schema: z6.literal(NARRATION_INDEX_SCHEMA),
363
+ speech: z6.object({
364
+ voice: z6.string().min(1),
365
+ voices: z6.array(z6.string().min(1)).optional(),
366
+ speed: z6.number().positive(),
367
+ dtype: z6.string().min(1),
368
+ pronunciations: z6.record(z6.string().min(1), z6.string()).optional()
309
369
  }),
310
- clips: z5.array(NarrationIndexClipSchema).min(1)
370
+ clips: z6.array(NarrationIndexClipSchema).min(1)
311
371
  });
312
372
 
313
373
  // ../schema/src/render-plan.ts
314
- import { z as z6 } from "zod";
315
- var CueSchema = z6.object({
316
- id: z6.string().min(1),
317
- startMs: z6.number().int().nonnegative(),
318
- endMs: z6.number().int().positive(),
319
- lines: z6.array(z6.string()).min(1).max(2)
374
+ import { z as z7 } from "zod";
375
+ var CueSchema = z7.object({
376
+ id: z7.string().min(1),
377
+ startMs: z7.number().int().nonnegative(),
378
+ endMs: z7.number().int().positive(),
379
+ lines: z7.array(z7.string()).min(1).max(2)
320
380
  });
321
- var HEX_COLOUR = z6.string().regex(/^#[0-9A-Fa-f]{6}$/, "must be a #RRGGBB colour");
322
- var OutroSchema = z6.object({
323
- durationMs: z6.number().int().positive().max(15e3),
324
- lines: z6.array(z6.string().min(1)).min(1).max(2),
381
+ var HEX_COLOUR = z7.string().regex(/^#[0-9A-Fa-f]{6}$/, "must be a #RRGGBB colour");
382
+ var OutroSchema = z7.object({
383
+ durationMs: z7.number().int().positive().max(15e3),
384
+ lines: z7.array(z7.string().min(1)).min(1).max(2),
325
385
  backgroundColor: HEX_COLOUR,
326
386
  textColor: HEX_COLOUR,
327
- assPath: z6.literal("captions/outro.ass")
387
+ assPath: z7.literal("captions/outro.ass")
328
388
  });
329
- var IntroSchema = z6.object({
330
- durationMs: z6.number().int().positive().max(15e3),
331
- lines: z6.array(z6.string().min(1)).min(1).max(2),
389
+ var IntroSchema = z7.object({
390
+ durationMs: z7.number().int().positive().max(15e3),
391
+ lines: z7.array(z7.string().min(1)).min(1).max(2),
332
392
  backgroundColor: HEX_COLOUR,
333
393
  textColor: HEX_COLOUR,
334
- assPath: z6.literal("captions/intro.ass")
394
+ assPath: z7.literal("captions/intro.ass")
335
395
  });
336
- var ChapterMarkSchema = z6.object({
337
- startMs: z6.number().int().nonnegative(),
338
- endMs: z6.number().int().positive(),
339
- title: z6.string().min(1)
396
+ var ChapterMarkSchema = z7.object({
397
+ startMs: z7.number().int().nonnegative(),
398
+ endMs: z7.number().int().positive(),
399
+ title: z7.string().min(1)
340
400
  });
341
- var ChaptersSchema = z6.object({
342
- metadataPath: z6.literal("render/chapters.ffmetadata"),
343
- marks: z6.array(ChapterMarkSchema).min(1)
401
+ var ChaptersSchema = z7.object({
402
+ metadataPath: z7.literal("render/chapters.ffmetadata"),
403
+ marks: z7.array(ChapterMarkSchema).min(1)
344
404
  }).refine(
345
405
  ({ marks }) => marks[0]?.startMs === 0 && marks.every(
346
406
  (mark, index) => mark.endMs > mark.startMs && (index === 0 || marks[index - 1]?.endMs === mark.startMs)
@@ -353,14 +413,14 @@ var ChaptersSchema = z6.object({
353
413
  path: ["marks"]
354
414
  }
355
415
  );
356
- var CursorPointSchema = z6.object({
357
- id: z6.string().min(1),
358
- x: z6.number().int().min(0).max(1920),
359
- y: z6.number().int().min(0).max(1080),
360
- arriveMs: z6.number().int().nonnegative(),
361
- departMs: z6.number().int().nonnegative(),
362
- action: z6.enum(["click", "type", "point"]),
363
- rippleMs: z6.number().int().nonnegative().optional(),
416
+ var CursorPointSchema = z7.object({
417
+ id: z7.string().min(1),
418
+ x: z7.number().int().min(0).max(1920),
419
+ y: z7.number().int().min(0).max(1080),
420
+ arriveMs: z7.number().int().nonnegative(),
421
+ departMs: z7.number().int().nonnegative(),
422
+ action: z7.enum(["click", "type", "point"]),
423
+ rippleMs: z7.number().int().nonnegative().optional(),
364
424
  /**
365
425
  * Size-correction percent, applied by the emitter with `\fscx`/`\fscy`, so the arrow
366
426
  * stays one size on screen whatever the frame does to it between the cursor filter and
@@ -391,11 +451,11 @@ var CursorPointSchema = z6.object({
391
451
  * always did — so a cursor bundle from before the camera, or from before the letterbox,
392
452
  * still parses and regenerates byte-for-byte.
393
453
  */
394
- scale: z6.number().int().min(1).max(400).optional()
454
+ scale: z7.number().int().min(1).max(400).optional()
395
455
  });
396
- var CursorSchema = z6.object({
397
- assPath: z6.literal("captions/cursor.ass"),
398
- points: z6.array(CursorPointSchema).min(1)
456
+ var CursorSchema = z7.object({
457
+ assPath: z7.literal("captions/cursor.ass"),
458
+ points: z7.array(CursorPointSchema).min(1)
399
459
  }).refine(
400
460
  ({ points }) => points.every(
401
461
  (point, index) => point.arriveMs <= point.departMs && (point.rippleMs === void 0 || point.rippleMs >= point.arriveMs) && (index === 0 || points[index - 1].departMs <= point.arriveMs && points[index - 1].arriveMs < point.arriveMs)
@@ -408,18 +468,18 @@ var CursorSchema = z6.object({
408
468
  path: ["points"]
409
469
  }
410
470
  );
411
- var CameraShotSchema = z6.object({
412
- id: z6.string().min(1),
413
- enterMs: z6.number().int().nonnegative(),
414
- holdFromMs: z6.number().int().nonnegative(),
415
- x: z6.number().int().min(0),
416
- y: z6.number().int().min(0),
417
- w: z6.number().int().positive(),
418
- h: z6.number().int().positive()
471
+ var CameraShotSchema = z7.object({
472
+ id: z7.string().min(1),
473
+ enterMs: z7.number().int().nonnegative(),
474
+ holdFromMs: z7.number().int().nonnegative(),
475
+ x: z7.number().int().min(0),
476
+ y: z7.number().int().min(0),
477
+ w: z7.number().int().positive(),
478
+ h: z7.number().int().positive()
419
479
  });
420
- var CameraSchema = z6.object({
421
- commandPath: z6.literal("render/camera.cmd"),
422
- shots: z6.array(CameraShotSchema).min(1)
480
+ var CameraSchema = z7.object({
481
+ commandPath: z7.literal("render/camera.cmd"),
482
+ shots: z7.array(CameraShotSchema).min(1)
423
483
  }).refine(
424
484
  ({ shots }) => shots.every(
425
485
  (shot) => shot.x % 2 === 0 && shot.y % 2 === 0 && // Implied by the %32 and 9w/16 rules below; kept so a future relaxation of
@@ -447,39 +507,39 @@ var CameraSchema = z6.object({
447
507
  path: ["shots"]
448
508
  }
449
509
  );
450
- var ActorSchema = z6.object({
451
- id: z6.string().min(1),
452
- label: z6.string().min(1)
510
+ var ActorSchema = z7.object({
511
+ id: z7.string().min(1),
512
+ label: z7.string().min(1)
453
513
  });
454
- var SegmentSchema = z6.object({
455
- id: z6.string().min(1),
456
- actorId: z6.string().min(1),
457
- startMs: z6.number().int().nonnegative(),
458
- endMs: z6.number().int().positive()
514
+ var SegmentSchema = z7.object({
515
+ id: z7.string().min(1),
516
+ actorId: z7.string().min(1),
517
+ startMs: z7.number().int().nonnegative(),
518
+ endMs: z7.number().int().positive()
459
519
  });
460
- var TransitionCardSchema = z6.object({
461
- fromActorId: z6.string().min(1),
462
- toActorId: z6.string().min(1),
463
- durationMs: z6.number().int().positive().max(15e3),
464
- lines: z6.array(z6.string().min(1)).min(1).max(2),
520
+ var TransitionCardSchema = z7.object({
521
+ fromActorId: z7.string().min(1),
522
+ toActorId: z7.string().min(1),
523
+ durationMs: z7.number().int().positive().max(15e3),
524
+ lines: z7.array(z7.string().min(1)).min(1).max(2),
465
525
  backgroundColor: HEX_COLOUR,
466
526
  textColor: HEX_COLOUR,
467
- assPath: z6.string().regex(/^captions\/turn-[0-9]+\.ass$/)
527
+ assPath: z7.string().regex(/^captions\/turn-[0-9]+\.ass$/)
468
528
  });
469
- var ExplainSegmentPlanSchema = z6.object({
470
- id: z6.string().min(1),
471
- segmentPath: z6.string().regex(/^explain\/[a-z0-9][a-z0-9-]*\/segment\.mp4$/),
472
- durationMs: z6.number().int().positive()
529
+ var ExplainSegmentPlanSchema = z7.object({
530
+ id: z7.string().min(1),
531
+ segmentPath: z7.string().regex(/^explain\/[a-z0-9][a-z0-9-]*\/segment\.mp4$/),
532
+ durationMs: z7.number().int().positive()
473
533
  });
474
- var HighlightRectSchema = z6.object({
475
- id: z6.string().min(1),
476
- x: z6.number().int().min(0),
477
- y: z6.number().int().min(0),
478
- width: z6.number().int().positive(),
479
- height: z6.number().int().positive(),
480
- startMs: z6.number().int().nonnegative(),
481
- endMs: z6.number().int().positive(),
482
- label: z6.string().min(1).optional(),
534
+ var HighlightRectSchema = z7.object({
535
+ id: z7.string().min(1),
536
+ x: z7.number().int().min(0),
537
+ y: z7.number().int().min(0),
538
+ width: z7.number().int().positive(),
539
+ height: z7.number().int().positive(),
540
+ startMs: z7.number().int().nonnegative(),
541
+ endMs: z7.number().int().positive(),
542
+ label: z7.string().min(1).optional(),
483
543
  /**
484
544
  * The rect the spotlight glides *from*, when this window's cutout should animate rather
485
545
  * than cut straight to the held `x`/`y`/`width`/`height` — the pre-reveal position a
@@ -491,11 +551,11 @@ var HighlightRectSchema = z6.object({
491
551
  * whole span — so every plan frozen before this field existed, and every window a future
492
552
  * derivation decides not to glide, is unaffected by construction.
493
553
  */
494
- from: z6.object({
495
- x: z6.number().int().min(0),
496
- y: z6.number().int().min(0),
497
- width: z6.number().int().positive(),
498
- height: z6.number().int().positive()
554
+ from: z7.object({
555
+ x: z7.number().int().min(0),
556
+ y: z7.number().int().min(0),
557
+ width: z7.number().int().positive(),
558
+ height: z7.number().int().positive()
499
559
  }).optional(),
500
560
  /**
501
561
  * The absolute video-ms at which the glide from `from` completes and the spotlight
@@ -504,7 +564,7 @@ var HighlightRectSchema = z6.object({
504
564
  * has to be real time left to glide across) and no later than `endMs` (the window
505
565
  * cannot settle after it has already closed).
506
566
  */
507
- settleMs: z6.number().int().positive().optional()
567
+ settleMs: z7.number().int().positive().optional()
508
568
  }).refine(({ x, y, width, height }) => x + width <= 1920 && y + height <= 1080, {
509
569
  // Unlike CameraShotSchema's crop window, a highlight rect is drawn by libass, not fed to
510
570
  // `crop` — there is no yuv420p even-pixel rule to enforce — but it still has to fit the
@@ -534,9 +594,9 @@ var HighlightRectSchema = z6.object({
534
594
  message: "settleMs must be strictly after startMs and no later than endMs",
535
595
  path: ["settleMs"]
536
596
  });
537
- var HighlightSchema = z6.object({
538
- assPath: z6.literal("captions/highlight.ass"),
539
- rects: z6.array(HighlightRectSchema).min(1)
597
+ var HighlightSchema = z7.object({
598
+ assPath: z7.literal("captions/highlight.ass"),
599
+ rects: z7.array(HighlightRectSchema).min(1)
540
600
  }).refine(
541
601
  ({ rects }) => rects.every(
542
602
  (rect, index) => rect.endMs > rect.startMs && (index === 0 || rects[index - 1].endMs <= rect.startMs)
@@ -546,8 +606,8 @@ var HighlightSchema = z6.object({
546
606
  path: ["rects"]
547
607
  }
548
608
  );
549
- var SpeechClipSchema = z6.object({
550
- id: z6.string().min(1),
609
+ var SpeechClipSchema = z7.object({
610
+ id: z7.string().min(1),
551
611
  /*
552
612
  * Bundle-relative and unescapable by construction: no slash, no leading dot, so `..` and
553
613
  * an absolute path are both unrepresentable. The renderer's `assertBundleRelative` says
@@ -561,10 +621,10 @@ var SpeechClipSchema = z6.object({
561
621
  * character the recorder could have replaced. `narration.test.ts` asserts the two agree on a
562
622
  * table of candidates, because a comment cannot.
563
623
  */
564
- path: z6.string().regex(/^speech\/clips\/[A-Za-z0-9][A-Za-z0-9._-]*\.wav$/),
565
- atMs: z6.number().int().nonnegative(),
566
- durationMs: z6.number().int().positive(),
567
- source: z6.enum(["synth", "file", "explain"]),
624
+ path: z7.string().regex(/^speech\/clips\/[A-Za-z0-9][A-Za-z0-9._-]*\.wav$/),
625
+ atMs: z7.number().int().nonnegative(),
626
+ durationMs: z7.number().int().positive(),
627
+ source: z7.enum(["synth", "file", "explain"]),
568
628
  /*
569
629
  * Which voice synthesised this clip, when it was not the plan-wide default that
570
630
  * `engine.voice` names. Absent on every clip of a one-voice recording and on every
@@ -580,16 +640,16 @@ var SpeechClipSchema = z6.object({
580
640
  * Optional and additive: every bundle frozen before per-step voices existed carries no
581
641
  * `voice` on any clip and must keep verifying, which is what `optional()` buys.
582
642
  */
583
- voice: z6.string().min(1).optional()
643
+ voice: z7.string().min(1).optional()
584
644
  });
585
- var SpeechEngineSchema = z6.object({
586
- name: z6.string().min(1),
587
- version: z6.string().min(1),
588
- modelSha256: z6.string().regex(/^[0-9a-f]{64}$/),
589
- voice: z6.string().min(1)
645
+ var SpeechEngineSchema = z7.object({
646
+ name: z7.string().min(1),
647
+ version: z7.string().min(1),
648
+ modelSha256: z7.string().regex(/^[0-9a-f]{64}$/),
649
+ voice: z7.string().min(1)
590
650
  });
591
- var SpeechSchema = z6.object({
592
- trackPath: z6.literal("speech/narration.wav"),
651
+ var SpeechSchema = z7.object({
652
+ trackPath: z7.literal("speech/narration.wav"),
593
653
  /*
594
654
  * 24 kHz mono, because that is what Kokoro emits and nothing on this path resamples.
595
655
  * Written as literals rather than imported from `@plaintake/audio`, which owns the
@@ -597,9 +657,9 @@ var SpeechSchema = z6.object({
597
657
  * be the cycle `no-circular` refuses. `packages/audio/src/wav.test.ts` asserts the two
598
658
  * agree, since a comment cannot.
599
659
  */
600
- sampleRate: z6.literal(24e3),
601
- channels: z6.literal(1),
602
- clips: z6.array(SpeechClipSchema).min(1),
660
+ sampleRate: z7.literal(24e3),
661
+ channels: z7.literal(1),
662
+ clips: z7.array(SpeechClipSchema).min(1),
603
663
  engine: SpeechEngineSchema.optional()
604
664
  }).refine(
605
665
  ({ clips }) => new Set(clips.map((clip) => clip.id)).size === clips.length && clips.every(
@@ -625,14 +685,14 @@ var SpeechSchema = z6.object({
625
685
  message: "engine must be recorded whenever any clip was synthesised",
626
686
  path: ["engine"]
627
687
  });
628
- var ThemeSchema = z6.object({ accentColor: HEX_COLOUR });
688
+ var ThemeSchema = z7.object({ accentColor: HEX_COLOUR });
629
689
  var RENDER_PLAN_SCHEMA = "agent-demo.render/v1";
630
- var RenderPlanSchema = z6.object({
631
- schema: z6.literal(RENDER_PLAN_SCHEMA),
632
- source: z6.object({
633
- path: z6.literal("raw/session.webm"),
634
- sha256: z6.string().regex(/^[0-9a-f]{64}$/),
635
- durationMs: z6.number().int().nonnegative()
690
+ var RenderPlanSchema = z7.object({
691
+ schema: z7.literal(RENDER_PLAN_SCHEMA),
692
+ source: z7.object({
693
+ path: z7.literal("raw/session.webm"),
694
+ sha256: z7.string().regex(/^[0-9a-f]{64}$/),
695
+ durationMs: z7.number().int().nonnegative()
636
696
  }),
637
697
  /**
638
698
  * The frame. **Three geometries live here and they are not the same thing**, which is the
@@ -694,17 +754,17 @@ var RenderPlanSchema = z6.object({
694
754
  * one would be backwards — a plan is frozen once and rendered later, so both belong at the
695
755
  * parse, not at the encode.
696
756
  */
697
- video: z6.object({
698
- width: z6.number().int().positive().multipleOf(2),
699
- height: z6.number().int().positive().multipleOf(2),
700
- fps: z6.literal(30),
701
- pixelFormat: z6.literal("yuv420p"),
702
- capture: z6.object({ width: z6.literal(1920), height: z6.literal(1080) }).default({ width: 1920, height: 1080 }),
703
- letterbox: z6.object({
704
- width: z6.number().int().positive().multipleOf(2),
705
- height: z6.number().int().positive().multipleOf(2),
706
- x: z6.number().int().nonnegative().multipleOf(2),
707
- y: z6.number().int().nonnegative().multipleOf(2),
757
+ video: z7.object({
758
+ width: z7.number().int().positive().multipleOf(2),
759
+ height: z7.number().int().positive().multipleOf(2),
760
+ fps: z7.literal(30),
761
+ pixelFormat: z7.literal("yuv420p"),
762
+ capture: z7.object({ width: z7.literal(1920), height: z7.literal(1080) }).default({ width: 1920, height: 1080 }),
763
+ letterbox: z7.object({
764
+ width: z7.number().int().positive().multipleOf(2),
765
+ height: z7.number().int().positive().multipleOf(2),
766
+ x: z7.number().int().nonnegative().multipleOf(2),
767
+ y: z7.number().int().nonnegative().multipleOf(2),
708
768
  /**
709
769
  * The fill for everything the box does not cover. `#RRGGBB` for the reason
710
770
  * `OutroSchema`'s colours are: the value reaches an FFmpeg filtergraph, and that
@@ -727,8 +787,8 @@ var RenderPlanSchema = z6.object({
727
787
  * With no intro it is exactly what it always was, which is what keeps a plan without
728
788
  * one byte-identical to what earlier versions wrote.
729
789
  */
730
- durationMs: z6.number().int().positive(),
731
- tailPadMs: z6.number().int().nonnegative()
790
+ durationMs: z7.number().int().positive(),
791
+ tailPadMs: z7.number().int().nonnegative()
732
792
  }).refine(
733
793
  ({ width, height, letterbox }) => letterbox === void 0 || letterbox.x + letterbox.width <= width && letterbox.y + letterbox.height <= height,
734
794
  {
@@ -742,12 +802,12 @@ var RenderPlanSchema = z6.object({
742
802
  path: ["letterbox"]
743
803
  }
744
804
  ),
745
- captions: z6.object({
746
- language: z6.string().min(2),
747
- srtPath: z6.literal("captions/captions.srt"),
748
- vttPath: z6.literal("captions/captions.vtt"),
749
- assPath: z6.literal("captions/captions.ass"),
750
- cues: z6.array(CueSchema)
805
+ captions: z7.object({
806
+ language: z7.string().min(2),
807
+ srtPath: z7.literal("captions/captions.srt"),
808
+ vttPath: z7.literal("captions/captions.vtt"),
809
+ assPath: z7.literal("captions/captions.ass"),
810
+ cues: z7.array(CueSchema)
751
811
  }),
752
812
  /**
753
813
  * How the burned-in captions were drawn. This is the **source** of
@@ -761,18 +821,18 @@ var RenderPlanSchema = z6.object({
761
821
  * older reader ignoring fields it does not know still executes the same frozen arguments
762
822
  * over the same frozen ASS. That observation generalises to the schema as a whole.
763
823
  */
764
- style: z6.object({
765
- fontFile: z6.literal("assets/fonts/NotoSans-Regular.ttf"),
766
- fontName: z6.literal("Noto Sans"),
767
- fontSize: z6.number().int().positive(),
768
- textColor: z6.string(),
769
- outlineColor: z6.string(),
824
+ style: z7.object({
825
+ fontFile: z7.literal("assets/fonts/NotoSans-Regular.ttf"),
826
+ fontName: z7.literal("Noto Sans"),
827
+ fontSize: z7.number().int().positive(),
828
+ textColor: z7.string(),
829
+ outlineColor: z7.string(),
770
830
  /** ASS `Outline`: a stroke width under `BorderStyle 1`, box padding under `BorderStyle 4`. */
771
- outlineWidth: z6.number().int().nonnegative(),
772
- marginBottom: z6.number().int().nonnegative(),
773
- marginSide: z6.number().int().nonnegative().default(80),
774
- borderStyle: z6.union([z6.literal(1), z6.literal(4)]).default(1),
775
- outlineOpacity: z6.number().min(0).max(1).default(1),
831
+ outlineWidth: z7.number().int().nonnegative(),
832
+ marginBottom: z7.number().int().nonnegative(),
833
+ marginSide: z7.number().int().nonnegative().default(80),
834
+ borderStyle: z7.union([z7.literal(1), z7.literal(4)]).default(1),
835
+ outlineOpacity: z7.number().min(0).max(1).default(1),
776
836
  boxColor: HEX_COLOUR.default("#000000"),
777
837
  /*
778
838
  * Opaque, which under the `BorderStyle 1` default draws nothing at all: `BackColour` is
@@ -780,7 +840,7 @@ var RenderPlanSchema = z6.object({
780
840
  * and ASS's own default, so a plan frozen before this field existed regenerates its ASS
781
841
  * byte-for-byte rather than byte-for-byte-except-one-field-nobody-can-see.
782
842
  */
783
- boxOpacity: z6.number().min(0).max(1).default(1),
843
+ boxOpacity: z7.number().min(0).max(1).default(1),
784
844
  /**
785
845
  * ASS `\an` alignment: which edge of the frame the caption block is anchored to, and
786
846
  * therefore which margin `marginBottom` is measured from.
@@ -799,12 +859,12 @@ var RenderPlanSchema = z6.object({
799
859
  * remaining code, middle-centre (5), would put the words back over the picture — which
800
860
  * is the thing the letterbox exists to stop.
801
861
  */
802
- alignment: z6.union([z6.literal(2), z6.literal(8)]).default(2)
862
+ alignment: z7.union([z7.literal(2), z7.literal(8)]).default(2)
803
863
  }),
804
- ffmpeg: z6.object({
805
- base: z6.array(z6.string()),
806
- soft: z6.array(z6.string()),
807
- hard: z6.array(z6.string())
864
+ ffmpeg: z7.object({
865
+ base: z7.array(z7.string()),
866
+ soft: z7.array(z7.string()),
867
+ hard: z7.array(z7.string())
808
868
  }),
809
869
  intro: IntroSchema.optional(),
810
870
  outro: OutroSchema.optional(),
@@ -838,6 +898,31 @@ var RenderPlanSchema = z6.object({
838
898
  * older binary's own re-parse has stripped `theme` out of.
839
899
  */
840
900
  theme: ThemeSchema.optional(),
901
+ /**
902
+ * The one video this plan renders to, named for the scenario: `output/create-api-key.mp4`
903
+ * for `id: 'create-api-key'`. Frozen once, at record time, from the scenario's own id —
904
+ * `ScenarioMetaSchema`'s lowercase-kebab id rule is what the path regex above mirrors, so
905
+ * the two cannot drift apart silently.
906
+ *
907
+ * Optional and **never defaulted**, for the same reason as `theme`: a Zod `.default()`
908
+ * materialises on parse, and every parse-and-rewrite of an older plan (a `--aspect`
909
+ * re-cut re-freezes `render/render-plan.json`) would inject the field into bundles
910
+ * recorded before per-scenario names existed. Absent means exactly that — a plan frozen
911
+ * before 1.21, whose video is the historical `output/demo.mp4` the renderer's arg
912
+ * builders fall back to.
913
+ *
914
+ * Top-level — a sibling of `theme`, not a key inside `video` (which `recutPlan` rewrites
915
+ * wholesale) — so `recutPlan`'s `{...plan}` spread carries it through `--aspect` re-cuts
916
+ * unchanged.
917
+ *
918
+ * The frozen `ffmpeg` arrays remain the runtime source of truth: `renderBundle` replays
919
+ * them verbatim and `outputPathOf` reads the destination back out of them, which is what
920
+ * lets a bundle frozen under any earlier naming scheme render unchanged. This field
921
+ * exists for the *re*-freezes — `recutPlan` and the multi-actor splice rebuild their
922
+ * argument arrays from the plan, and read this field (via `outputOf`) so a re-cut writes
923
+ * the file the original recording wrote rather than the fallback name.
924
+ */
925
+ outputPath: z7.string().regex(/^output\/[a-z0-9][a-z0-9-]*\.mp4$/, "must be output/<scenario-id>.mp4, a kebab-case id").optional(),
841
926
  /**
842
927
  * The capture timeline cut into windows — of a genuine multi-actor cast, of an implicit
843
928
  * single actor pausing for explain cut-aways, or both at once. `segments` is the one
@@ -861,11 +946,11 @@ var RenderPlanSchema = z6.object({
861
946
  * it out): a lone, unsegmented capture has no gap to claim in the first place, and needs
862
947
  * none of these five fields at all.
863
948
  */
864
- actors: z6.array(ActorSchema).min(2).optional(),
865
- segments: z6.array(SegmentSchema).min(2).optional(),
866
- transitionCards: z6.array(TransitionCardSchema).min(1).optional(),
867
- badgesAssPath: z6.literal("captions/badges.ass").optional(),
868
- explainSegments: z6.array(ExplainSegmentPlanSchema).min(1).optional()
949
+ actors: z7.array(ActorSchema).min(2).optional(),
950
+ segments: z7.array(SegmentSchema).min(2).optional(),
951
+ transitionCards: z7.array(TransitionCardSchema).min(1).optional(),
952
+ badgesAssPath: z7.literal("captions/badges.ass").optional(),
953
+ explainSegments: z7.array(ExplainSegmentPlanSchema).min(1).optional()
869
954
  }).refine(
870
955
  (plan) => {
871
956
  const castFields = [plan.actors, plan.transitionCards, plan.badgesAssPath];
@@ -936,95 +1021,95 @@ var RenderPlanSchema = z6.object({
936
1021
  );
937
1022
 
938
1023
  // ../schema/src/results.ts
939
- import { z as z7 } from "zod";
940
- var RELATIVE_POSIX = z7.string().regex(/^[A-Za-z0-9.][A-Za-z0-9._/-]*$/, "must be a relative POSIX path").refine((value) => !value.split("/").includes(".."), "must not traverse upwards");
941
- var Sha2562 = z7.string().regex(/^[0-9a-f]{64}$/);
942
- var DISPLAY_PATH = z7.string().min(1);
1024
+ import { z as z8 } from "zod";
1025
+ var RELATIVE_POSIX = z8.string().regex(/^[A-Za-z0-9.][A-Za-z0-9._/-]*$/, "must be a relative POSIX path").refine((value) => !value.split("/").includes(".."), "must not traverse upwards");
1026
+ var Sha2563 = z8.string().regex(/^[0-9a-f]{64}$/);
1027
+ var DISPLAY_PATH = z8.string().min(1);
943
1028
  var envelope = (kind) => ({
944
- schema: z7.literal("agent-demo.result/v1"),
945
- kind: z7.literal(kind),
946
- ok: z7.boolean(),
1029
+ schema: z8.literal("agent-demo.result/v1"),
1030
+ kind: z8.literal(kind),
1031
+ ok: z8.boolean(),
947
1032
  /** Human-readable failures. Empty iff ok. */
948
- problems: z7.array(z7.string())
1033
+ problems: z8.array(z8.string())
949
1034
  });
950
- var ArtifactRefSchema = z7.object({
1035
+ var ArtifactRefSchema = z8.object({
951
1036
  path: RELATIVE_POSIX,
952
- sha256: Sha2562,
953
- bytes: z7.number().int().nonnegative()
1037
+ sha256: Sha2563,
1038
+ bytes: z8.number().int().nonnegative()
954
1039
  });
955
- var StreamSummarySchema = z7.object({
956
- container: z7.enum(["mp4", "webm", "other"]),
957
- durationMs: z7.number().int().nonnegative(),
958
- video: z7.object({
959
- codec: z7.string(),
960
- width: z7.number().int(),
961
- height: z7.number().int(),
962
- pixelFormat: z7.string(),
963
- fps: z7.number().int(),
964
- frames: z7.number().int(),
1040
+ var StreamSummarySchema = z8.object({
1041
+ container: z8.enum(["mp4", "webm", "other"]),
1042
+ durationMs: z8.number().int().nonnegative(),
1043
+ video: z8.object({
1044
+ codec: z8.string(),
1045
+ width: z8.number().int(),
1046
+ height: z8.number().int(),
1047
+ pixelFormat: z8.string(),
1048
+ fps: z8.number().int(),
1049
+ frames: z8.number().int(),
965
1050
  /**
966
1051
  * The video stream's own duration, as distinct from the container's above. The
967
1052
  * constant-rate check measures frames against this, because a padded narration track makes
968
1053
  * the container's duration the audio's. Zero for a WebM, which carries no per-track
969
1054
  * duration at all.
970
1055
  */
971
- durationMs: z7.number().int().nonnegative()
1056
+ durationMs: z8.number().int().nonnegative()
972
1057
  }),
973
- subtitles: z7.array(z7.object({ codec: z7.string(), language: z7.string(), title: z7.string() })),
1058
+ subtitles: z8.array(z8.object({ codec: z8.string(), language: z8.string(), title: z8.string() })),
974
1059
  /** Properties, not a count — a narration track muxed in stereo or at the wrong rate
975
1060
  * still counts as one stream. */
976
- audio: z7.array(
977
- z7.object({
978
- codec: z7.string(),
979
- sampleRate: z7.number().int().nonnegative(),
980
- channels: z7.number().int().nonnegative()
1061
+ audio: z8.array(
1062
+ z8.object({
1063
+ codec: z8.string(),
1064
+ sampleRate: z8.number().int().nonnegative(),
1065
+ channels: z8.number().int().nonnegative()
981
1066
  })
982
1067
  ),
983
1068
  /** Titled chapters make FFmpeg's MP4 muxer add a `bin_data` track beside the `chpl` atom. */
984
- dataStreams: z7.number().int().nonnegative(),
985
- chapters: z7.array(
986
- z7.object({
987
- startMs: z7.number().int().nonnegative(),
988
- endMs: z7.number().int().nonnegative(),
989
- title: z7.string()
1069
+ dataStreams: z8.number().int().nonnegative(),
1070
+ chapters: z8.array(
1071
+ z8.object({
1072
+ startMs: z8.number().int().nonnegative(),
1073
+ endMs: z8.number().int().nonnegative(),
1074
+ title: z8.string()
990
1075
  })
991
1076
  )
992
1077
  });
993
- var ToolchainSchema = z7.object({
994
- node: z7.string(),
995
- playwright: z7.string(),
996
- chromiumRevision: z7.string(),
997
- ffmpeg: z7.string(),
1078
+ var ToolchainSchema = z8.object({
1079
+ node: z8.string(),
1080
+ playwright: z8.string(),
1081
+ chromiumRevision: z8.string(),
1082
+ ffmpeg: z8.string(),
998
1083
  /** May be the literal "unknown" — never fabricated. */
999
- libass: z7.string(),
1000
- fontSha256: Sha2562
1084
+ libass: z8.string(),
1085
+ fontSha256: Sha2563
1001
1086
  });
1002
- var AssertionSchema = z7.object({ id: z7.string().min(1), status: z7.enum(["passed", "failed"]) });
1003
- var StatusSchema = z7.enum(["passed", "failed"]);
1004
- var VariantsSchema = z7.enum(["soft", "hard"]);
1005
- var ValidationResultSchema = z7.object({
1087
+ var AssertionSchema = z8.object({ id: z8.string().min(1), status: z8.enum(["passed", "failed"]) });
1088
+ var StatusSchema = z8.enum(["passed", "failed"]);
1089
+ var VariantsSchema = z8.enum(["soft", "hard"]);
1090
+ var ValidationResultSchema = z8.object({
1006
1091
  ...envelope("validate"),
1007
1092
  /** Absent when the scenario could not be loaded at all. */
1008
- scenario: z7.object({
1009
- id: z7.string(),
1010
- title: z7.string(),
1011
- language: z7.string(),
1093
+ scenario: z8.object({
1094
+ id: z8.string(),
1095
+ title: z8.string(),
1096
+ language: z8.string(),
1012
1097
  /**
1013
1098
  * Whether this scenario needs a person, and in which phase. Reported so an author
1014
1099
  * — or an agent reading `--json` — learns that a scenario cannot run unattended
1015
1100
  * without first trying to run it. Defaulted, so a result written before this parses.
1016
1101
  */
1017
- handoff: z7.enum(["none", "preflight", "session"]).default("none"),
1018
- sha256: Sha2562
1102
+ handoff: z8.enum(["none", "preflight", "session"]).default("none"),
1103
+ sha256: Sha2563
1019
1104
  }).optional()
1020
1105
  });
1021
- var RunCommandResultSchema = z7.object({
1106
+ var RunCommandResultSchema = z8.object({
1022
1107
  ...envelope("run"),
1023
1108
  bundleDir: DISPLAY_PATH,
1024
1109
  status: StatusSchema,
1025
- cueCount: z7.number().int().nonnegative(),
1110
+ cueCount: z8.number().int().nonnegative(),
1026
1111
  /** Zero on the Free Tier, and zero for a scenario with no `chapter` calls. */
1027
- chapterCount: z7.number().int().nonnegative(),
1112
+ chapterCount: z8.number().int().nonnegative(),
1028
1113
  /**
1029
1114
  * How many lines the video speaks. Zero for a silent run, which is the default.
1030
1115
  *
@@ -1033,119 +1118,130 @@ var RunCommandResultSchema = z7.object({
1033
1118
  * scenario whose steps carry no `subtitle`, which produces a perfectly valid silent video and
1034
1119
  * would look like the feature being broken rather than like nothing having been written to say.
1035
1120
  */
1036
- narratedCount: z7.number().int().nonnegative(),
1037
- diagnostics: z7.array(z7.object({ code: z7.string(), cueId: z7.string(), detail: z7.string() })),
1038
- assertions: z7.array(AssertionSchema),
1039
- outputs: z7.array(ArtifactRefSchema),
1121
+ narratedCount: z8.number().int().nonnegative(),
1122
+ diagnostics: z8.array(z8.object({ code: z8.string(), cueId: z8.string(), detail: z8.string() })),
1123
+ assertions: z8.array(AssertionSchema),
1124
+ outputs: z8.array(ArtifactRefSchema),
1040
1125
  /**
1041
1126
  * The previous bundle this run replaced, archived as a `.<timestamp>` sibling rather
1042
1127
  * than deleted. Absent when nothing occupied the output directory — the common first
1043
1128
  * run — or when `--no-archive` asked for the old replace semantics. Optional and
1044
1129
  * additive, so every result written before archives existed still parses.
1045
1130
  */
1046
- archivedPath: DISPLAY_PATH.optional()
1131
+ archivedPath: DISPLAY_PATH.optional(),
1132
+ /**
1133
+ * The capture-drift verdict against `<name>.baseline.json` beside the scenario. Absent when
1134
+ * the recording failed (a failed recording is not evidence of drift) and in every result
1135
+ * written before baselines existed, so it is optional and additive.
1136
+ */
1137
+ baseline: BaselineReportSchema.optional()
1047
1138
  });
1048
- var CheckAssertionSchema = AssertionSchema.extend({ message: z7.string().optional() });
1049
- var CheckCommandResultSchema = z7.object({
1139
+ var CheckAssertionSchema = AssertionSchema.extend({ message: z8.string().optional() });
1140
+ var CheckCommandResultSchema = z8.object({
1050
1141
  ...envelope("check"),
1051
1142
  bundleDir: DISPLAY_PATH,
1052
1143
  status: StatusSchema,
1053
- assertions: z7.array(CheckAssertionSchema),
1144
+ assertions: z8.array(CheckAssertionSchema),
1054
1145
  /**
1055
1146
  * Handoff notes, plus a `speech.close` note when closing the narrator failed after
1056
1147
  * capture. Cue, chapter, cursor and camera diagnostics are all derived from a render plan
1057
1148
  * that `check` never builds — fabricating them would report on capabilities this command
1058
1149
  * cannot see.
1059
1150
  */
1060
- diagnostics: z7.array(z7.object({ code: z7.string(), cueId: z7.string(), detail: z7.string() }))
1151
+ diagnostics: z8.array(z8.object({ code: z8.string(), cueId: z8.string(), detail: z8.string() })),
1152
+ /**
1153
+ * The capture-drift verdict against `<name>.baseline.json` beside the scenario. Absent when
1154
+ * the recording failed (a failed recording is not evidence of drift) and in every result
1155
+ * written before baselines existed, so it is optional and additive.
1156
+ */
1157
+ baseline: BaselineReportSchema.optional()
1061
1158
  });
1062
- var RenderCommandResultSchema = z7.object({
1159
+ var RenderCommandResultSchema = z8.object({
1063
1160
  ...envelope("render"),
1064
1161
  bundleDir: DISPLAY_PATH,
1065
1162
  variants: VariantsSchema,
1066
- streams: z7.object({
1163
+ streams: z8.object({
1067
1164
  base: StreamSummarySchema.optional(),
1068
1165
  soft: StreamSummarySchema.optional(),
1069
1166
  hard: StreamSummarySchema.optional()
1070
1167
  }),
1071
- outputs: z7.array(ArtifactRefSchema)
1168
+ outputs: z8.array(ArtifactRefSchema)
1072
1169
  });
1073
- var VerificationReportSchema = z7.object({
1170
+ var VerificationReportSchema = z8.object({
1074
1171
  ...envelope("verify"),
1075
1172
  bundleDir: DISPLAY_PATH,
1076
- artifactCount: z7.number().int().nonnegative()
1173
+ artifactCount: z8.number().int().nonnegative()
1077
1174
  });
1078
- var WarmCommandResultSchema = z7.object({
1175
+ var WarmCommandResultSchema = z8.object({
1079
1176
  ...envelope("warm"),
1080
1177
  bundleDir: DISPLAY_PATH,
1081
- clipCount: z7.number().int().nonnegative(),
1082
- synthesizedCount: z7.number().int().nonnegative(),
1083
- cachedCount: z7.number().int().nonnegative()
1178
+ clipCount: z8.number().int().nonnegative(),
1179
+ synthesizedCount: z8.number().int().nonnegative(),
1180
+ cachedCount: z8.number().int().nonnegative()
1084
1181
  });
1085
- var DifferenceCategorySchema = z7.enum(["step", "assertion", "target", "timing", "caption", "actor"]);
1086
- var DiffCommandResultSchema = z7.object({
1182
+ var DiffCommandResultSchema = z8.object({
1087
1183
  ...envelope("diff"),
1088
1184
  bundleA: DISPLAY_PATH,
1089
1185
  bundleB: DISPLAY_PATH,
1090
- identical: z7.boolean(),
1091
- differences: z7.array(z7.object({ category: DifferenceCategorySchema, detail: z7.string() }))
1186
+ identical: z8.boolean(),
1187
+ differences: z8.array(DifferenceSchema)
1092
1188
  });
1093
- var CompareCommandResultSchema = z7.object({
1189
+ var CompareCommandResultSchema = z8.object({
1094
1190
  ...envelope("compare"),
1095
1191
  bundleA: DISPLAY_PATH,
1096
1192
  bundleB: DISPLAY_PATH,
1097
- checks: z7.array(z7.object({ name: z7.string(), ok: z7.boolean(), detail: z7.string() }))
1193
+ checks: z8.array(z8.object({ name: z8.string(), ok: z8.boolean(), detail: z8.string() }))
1098
1194
  });
1099
- var InitResultSchema = z7.object({
1195
+ var InitResultSchema = z8.object({
1100
1196
  ...envelope("init"),
1101
- files: z7.array(DISPLAY_PATH),
1102
- validates: z7.boolean()
1197
+ files: z8.array(DISPLAY_PATH),
1198
+ validates: z8.boolean()
1103
1199
  });
1104
- var PruneCandidateSchema = z7.object({
1200
+ var PruneCandidateSchema = z8.object({
1105
1201
  path: DISPLAY_PATH,
1106
- bytes: z7.number().int().nonnegative(),
1107
- scenarioId: z7.string().optional()
1202
+ bytes: z8.number().int().nonnegative(),
1203
+ scenarioId: z8.string().optional()
1108
1204
  });
1109
- var PruneCommandResultSchema = z7.object({
1205
+ var PruneCommandResultSchema = z8.object({
1110
1206
  ...envelope("prune"),
1111
- dryRun: z7.boolean(),
1207
+ dryRun: z8.boolean(),
1112
1208
  /** What was removed (confirmed) or would be (dry run) — never includes a `failed` entry. */
1113
- candidates: z7.array(PruneCandidateSchema),
1209
+ candidates: z8.array(PruneCandidateSchema),
1114
1210
  /** A discovered bundle that was never attempted, and why — never silently dropped. */
1115
- skipped: z7.array(z7.object({ path: DISPLAY_PATH, reason: z7.string() })),
1211
+ skipped: z8.array(z8.object({ path: DISPLAY_PATH, reason: z8.string() })),
1116
1212
  /** A selected candidate `rm` was attempted on and could not remove, and why. Always empty
1117
1213
  * on a dry run, since nothing is attempted until `--yes`. */
1118
- failed: z7.array(z7.object({ path: DISPLAY_PATH, reason: z7.string() })),
1214
+ failed: z8.array(z8.object({ path: DISPLAY_PATH, reason: z8.string() })),
1119
1215
  /** What `candidates` account for: bytes actually reclaimed if `dryRun` is false, or would
1120
1216
  * be reclaimed if every candidate succeeded. */
1121
- bytesReclaimed: z7.number().int().nonnegative()
1217
+ bytesReclaimed: z8.number().int().nonnegative()
1122
1218
  });
1123
- var ImportCommandResultSchema = z7.object({
1219
+ var ImportCommandResultSchema = z8.object({
1124
1220
  ...envelope("import"),
1125
1221
  tracePath: DISPLAY_PATH,
1126
1222
  outputPath: DISPLAY_PATH,
1127
1223
  /** How many `demo.step` calls the draft carries. Zero only on the failure path. */
1128
- stepCount: z7.number().int().nonnegative(),
1224
+ stepCount: z8.number().int().nonnegative(),
1129
1225
  /** Whether the written draft already passes `plaintake validate`. Best-effort: a draft
1130
1226
  * that fails is still written, still a success, and its problems are warnings here. */
1131
- validates: z7.boolean(),
1227
+ validates: z8.boolean(),
1132
1228
  /** Author-facing advice: multi-page traces, redactions, unimported trace calls, and the
1133
1229
  * standing review-for-secrets line. Never empty on success. */
1134
- warnings: z7.array(z7.string())
1230
+ warnings: z8.array(z8.string())
1135
1231
  });
1136
- var InspectResultSchema = z7.object({
1232
+ var InspectResultSchema = z8.object({
1137
1233
  ...envelope("inspect"),
1138
1234
  bundleDir: DISPLAY_PATH,
1139
1235
  status: StatusSchema,
1140
- scenario: z7.object({ id: z7.string(), sourceSha256: Sha2562 }),
1236
+ scenario: z8.object({ id: z8.string(), sourceSha256: Sha2563 }),
1141
1237
  toolchain: ToolchainSchema,
1142
- video: z7.object({
1238
+ video: z8.object({
1143
1239
  /** The whole output: the cards, if any, plus the content between them. */
1144
- durationMs: z7.number().int(),
1145
- frames: z7.number().int(),
1146
- width: z7.number().int(),
1147
- height: z7.number().int(),
1148
- fps: z7.number().int(),
1240
+ durationMs: z8.number().int(),
1241
+ frames: z8.number().int(),
1242
+ width: z8.number().int(),
1243
+ height: z8.number().int(),
1244
+ fps: z8.number().int(),
1149
1245
  /**
1150
1246
  * Split out because `durationMs` alone is misleading once a card exists: 12,034ms of
1151
1247
  * output can be 9,034ms of recording. The parts are reported separately rather than
@@ -1160,20 +1256,20 @@ var InspectResultSchema = z7.object({
1160
1256
  * the word means, and is worth stating because the plan's own `video.durationMs` is a
1161
1257
  * different number that includes the opening card.
1162
1258
  */
1163
- contentDurationMs: z7.number().int().nonnegative(),
1164
- introDurationMs: z7.number().int().nonnegative().default(0),
1165
- outroDurationMs: z7.number().int().nonnegative()
1259
+ contentDurationMs: z8.number().int().nonnegative(),
1260
+ introDurationMs: z8.number().int().nonnegative().default(0),
1261
+ outroDurationMs: z8.number().int().nonnegative()
1166
1262
  }),
1167
- captions: z7.object({
1168
- language: z7.string(),
1169
- cueCount: z7.number().int().nonnegative(),
1170
- characterCount: z7.number().int().nonnegative(),
1171
- medianCueMs: z7.number().int().nonnegative(),
1172
- shortestCueMs: z7.number().int().nonnegative(),
1173
- longestCueMs: z7.number().int().nonnegative()
1263
+ captions: z8.object({
1264
+ language: z8.string(),
1265
+ cueCount: z8.number().int().nonnegative(),
1266
+ characterCount: z8.number().int().nonnegative(),
1267
+ medianCueMs: z8.number().int().nonnegative(),
1268
+ shortestCueMs: z8.number().int().nonnegative(),
1269
+ longestCueMs: z8.number().int().nonnegative()
1174
1270
  }),
1175
1271
  /** Measured from the rendered MP4 where possible, declared from the plan otherwise. */
1176
- chapters: z7.array(z7.object({ startMs: z7.number().int().nonnegative(), title: z7.string() })),
1272
+ chapters: z8.array(z8.object({ startMs: z8.number().int().nonnegative(), title: z8.string() })),
1177
1273
  /**
1178
1274
  * The narration this bundle carries, or absent for a silent one.
1179
1275
  *
@@ -1184,13 +1280,13 @@ var InspectResultSchema = z7.object({
1184
1280
  * model behind it, and inventing a voice id for it would make the one field that answers "why
1185
1281
  * does this sound different" answer wrongly.
1186
1282
  */
1187
- narration: z7.object({
1188
- clipCount: z7.number().int().positive(),
1283
+ narration: z8.object({
1284
+ clipCount: z8.number().int().positive(),
1189
1285
  /** Total speech, excluding the silence between clips. Not the video's length. */
1190
- spokenMs: z7.number().int().nonnegative(),
1286
+ spokenMs: z8.number().int().nonnegative(),
1191
1287
  /** How many clips the author supplied rather than the model producing. */
1192
- suppliedCount: z7.number().int().nonnegative(),
1193
- voice: z7.string().optional()
1288
+ suppliedCount: z8.number().int().nonnegative(),
1289
+ voice: z8.string().optional()
1194
1290
  }).optional(),
1195
1291
  /**
1196
1292
  * The multi-actor demo's cast, or absent for a single-actor bundle — every bundle
@@ -1207,19 +1303,19 @@ var InspectResultSchema = z7.object({
1207
1303
  * the field a reader of this JSON would already trust as the timeline, not from a second
1208
1304
  * field that only agrees with it by a schema-level invariant this result does not surface.
1209
1305
  */
1210
- actors: z7.object({
1306
+ actors: z8.object({
1211
1307
  /** `plan.actors.length`. Reported alongside `labels` rather than left for a caller to
1212
1308
  * compute, matching `narration.clipCount` sitting beside `narration` above for the
1213
1309
  * same reason: the count is the fact most callers want first, before the list. */
1214
- count: z7.number().int().min(2),
1310
+ count: z8.number().int().min(2),
1215
1311
  /** In `plan.actors` order — the cast list a badge or transition card would draw, not
1216
1312
  * deduplicated or sorted. */
1217
- labels: z7.array(z7.string().min(1)).min(2),
1313
+ labels: z8.array(z8.string().min(1)).min(2),
1218
1314
  /** Hand-offs, not participants: one fewer than `plan.segments.length`, since a
1219
1315
  * segment is a window one actor already holds and a turn is the cut between two of
1220
1316
  * them — the same count `plan.transitionCards.length` carries, arrived at
1221
1317
  * independently from `segments` for the reason above. */
1222
- turnCount: z7.number().int().nonnegative()
1318
+ turnCount: z8.number().int().nonnegative()
1223
1319
  }).refine((value) => value.count === value.labels.length, "actors.count must equal actors.labels.length").optional(),
1224
1320
  /**
1225
1321
  * The explain scenes this bundle cut away to, in composite order, or absent for a bundle
@@ -1231,20 +1327,20 @@ var InspectResultSchema = z7.object({
1231
1327
  * `actors` is sourced from the plan rather than re-read from `demo.explain()` calls: a
1232
1328
  * bundle whose scenario has since been edited or deleted still inspects correctly.
1233
1329
  */
1234
- explainScenes: z7.array(
1235
- z7.object({
1236
- id: z7.string().min(1),
1237
- durationMs: z7.number().int().positive()
1330
+ explainScenes: z8.array(
1331
+ z8.object({
1332
+ id: z8.string().min(1),
1333
+ durationMs: z8.number().int().positive()
1238
1334
  })
1239
1335
  ).min(1).optional(),
1240
1336
  /** The rendered MP4s, from the manifest, so hashes are not recomputed. */
1241
- outputs: z7.array(ArtifactRefSchema),
1242
- assertions: z7.array(AssertionSchema),
1243
- artifactCount: z7.number().int().nonnegative(),
1337
+ outputs: z8.array(ArtifactRefSchema),
1338
+ assertions: z8.array(AssertionSchema),
1339
+ artifactCount: z8.number().int().nonnegative(),
1244
1340
  /** Everything the manifest accounts for. What "delete this bundle" would reclaim. */
1245
- bundleBytes: z7.number().int().nonnegative()
1341
+ bundleBytes: z8.number().int().nonnegative()
1246
1342
  });
1247
- var DoctorResultSchema = z7.object({
1343
+ var DoctorResultSchema = z8.object({
1248
1344
  ...envelope("doctor"),
1249
1345
  /**
1250
1346
  * The PlainTake build this is. Here rather than in `ToolchainSchema` on purpose:
@@ -1252,12 +1348,12 @@ var DoctorResultSchema = z7.object({
1252
1348
  * manifest on every release and break the byte-for-byte golden bundle. `doctor` is the
1253
1349
  * one command a bug report is asked for, which makes it the right place.
1254
1350
  */
1255
- version: z7.string(),
1256
- ffmpeg: z7.string(),
1257
- ffprobe: z7.string(),
1258
- libass: z7.string(),
1259
- hasLibass: z7.boolean(),
1260
- hasX264: z7.boolean(),
1351
+ version: z8.string(),
1352
+ ffmpeg: z8.string(),
1353
+ ffprobe: z8.string(),
1354
+ libass: z8.string(),
1355
+ hasLibass: z8.boolean(),
1356
+ hasX264: z8.boolean(),
1261
1357
  /**
1262
1358
  * Whether this FFmpeg can encode AAC, which is what a narrated bundle is muxed with.
1263
1359
  *
@@ -1267,8 +1363,8 @@ var DoctorResultSchema = z7.object({
1267
1363
  * that actually needs it, with a fix string. Failing `doctor` here would be telling someone
1268
1364
  * their installation is broken for a capability they may never turn on.
1269
1365
  */
1270
- hasAac: z7.boolean(),
1271
- filters: z7.array(z7.string()),
1366
+ hasAac: z8.boolean(),
1367
+ filters: z8.array(z8.string()),
1272
1368
  /**
1273
1369
  * Whether spoken narration could be produced right now, and what is missing if not.
1274
1370
  *
@@ -1277,12 +1373,12 @@ var DoctorResultSchema = z7.object({
1277
1373
  * `openNarrator` asks before a run, answered by the same function (`speechAssetProblems`), so
1278
1374
  * `doctor` cannot say speech is fine and then a run refuse it.
1279
1375
  */
1280
- speech: z7.object({
1281
- ready: z7.boolean(),
1376
+ speech: z8.object({
1377
+ ready: z8.boolean(),
1282
1378
  /** Installed voices, in catalogue order. Empty means the model cannot speak yet. */
1283
- voices: z7.array(z7.string()),
1379
+ voices: z8.array(z8.string()),
1284
1380
  /** What is missing, in the same words a refused run would use. Empty when ready. */
1285
- notes: z7.array(z7.string())
1381
+ notes: z8.array(z8.string())
1286
1382
  }),
1287
1383
  /**
1288
1384
  * Whether the plainmotion CLI is on `PATH`, for a scenario that wants to call
@@ -1293,16 +1389,16 @@ var DoctorResultSchema = z7.object({
1293
1389
  * probe a run's own first `demo.explain()` pays, so `doctor` cannot say plainmotion is fine
1294
1390
  * and then a run refuse it.
1295
1391
  */
1296
- plainmotion: z7.object({
1297
- installed: z7.boolean(),
1392
+ plainmotion: z8.object({
1393
+ installed: z8.boolean(),
1298
1394
  /** plainmotion's own `--version` output. Absent when not installed. */
1299
- version: z7.string().optional(),
1395
+ version: z8.string().optional(),
1300
1396
  /** Why the probe failed, in the same words a run's own refusal would use. Absent when installed. */
1301
- note: z7.string().optional()
1397
+ note: z8.string().optional()
1302
1398
  })
1303
1399
  });
1304
1400
  var okMatchesProblems = (value) => value.ok === (value.problems.length === 0);
1305
- var ActivateReasonSchema = z7.enum([
1401
+ var ActivateReasonSchema = z8.enum([
1306
1402
  "empty-key",
1307
1403
  "not-found",
1308
1404
  "revoked",
@@ -1310,16 +1406,16 @@ var ActivateReasonSchema = z7.enum([
1310
1406
  "unexpected-response",
1311
1407
  "not-configured"
1312
1408
  ]);
1313
- var ActivateResultSchema = z7.object({
1409
+ var ActivateResultSchema = z8.object({
1314
1410
  ...envelope("activate"),
1315
1411
  /** Present only when ok is false, so a machine reader can distinguish the classes. */
1316
1412
  reason: ActivateReasonSchema.optional(),
1317
1413
  /** Present only on success — a failed activation has no record. The key itself is
1318
1414
  * deliberately absent: the caller already has it, and stdout may be logged. */
1319
- licence: z7.object({
1320
- subject: z7.string().min(1),
1321
- activatedAt: z7.string().min(1),
1322
- uses: z7.number().int().nonnegative()
1415
+ licence: z8.object({
1416
+ subject: z8.string().min(1),
1417
+ activatedAt: z8.string().min(1),
1418
+ uses: z8.number().int().nonnegative()
1323
1419
  }).optional()
1324
1420
  }).refine(okMatchesProblems, "ok must be true iff problems is empty").refine(
1325
1421
  // Making the "present only when" comments above literally true, so a reader can
@@ -1327,22 +1423,67 @@ var ActivateResultSchema = z7.object({
1327
1423
  (value) => value.reason !== void 0 === !value.ok && (value.licence === void 0 || value.ok),
1328
1424
  "reason appears iff ok is false, and licence only on success"
1329
1425
  );
1330
- var LicenceResultSchema = z7.discriminatedUnion("tier", [
1331
- z7.object({
1426
+ var LicenceResultSchema = z8.discriminatedUnion("tier", [
1427
+ z8.object({
1332
1428
  ...envelope("licence"),
1333
- tier: z7.literal("free"),
1429
+ tier: z8.literal("free"),
1334
1430
  /**
1335
1431
  * Why a present-but-unusable record degraded to free, in readLicense's own words.
1336
1432
  * Must never quote a filesystem path: assertNoAbsolutePaths exempts only `problems`.
1337
1433
  */
1338
- problem: z7.string().optional()
1434
+ problem: z8.string().optional()
1339
1435
  }),
1340
- z7.object({
1436
+ z8.object({
1341
1437
  ...envelope("licence"),
1342
- tier: z7.literal("pro"),
1343
- subject: z7.string().min(1),
1344
- activatedAt: z7.string().min(1),
1345
- uses: z7.number().int().nonnegative()
1438
+ tier: z8.literal("pro"),
1439
+ subject: z8.string().min(1),
1440
+ activatedAt: z8.string().min(1),
1441
+ uses: z8.number().int().nonnegative()
1442
+ })
1443
+ ]).refine(okMatchesProblems, "ok must be true iff problems is empty");
1444
+ var PublishReasonSchema = z8.enum([
1445
+ "no-endpoint",
1446
+ "no-key",
1447
+ "invalid-store",
1448
+ "not-a-bundle",
1449
+ "unauthorized",
1450
+ "unreachable",
1451
+ "hash-mismatch",
1452
+ "length-mismatch",
1453
+ "conflict",
1454
+ "unexpected-response"
1455
+ ]);
1456
+ var PublishResultSchema = z8.discriminatedUnion("ok", [
1457
+ z8.object({
1458
+ ...envelope("publish"),
1459
+ ok: z8.literal(false),
1460
+ reason: PublishReasonSchema
1461
+ }),
1462
+ z8.object({
1463
+ ...envelope("publish"),
1464
+ ok: z8.literal(true),
1465
+ videoId: z8.string().regex(/^[a-z2-7]{26}$/, "must be a 26-char base32 share id"),
1466
+ /** The page viewers open — the endpoint plus the id. */
1467
+ url: z8.string().regex(/^https?:\/\/\S+$/, "must be an http or https URL"),
1468
+ /** False when the video was already on the service — nothing was uploaded. */
1469
+ uploaded: z8.boolean(),
1470
+ /**
1471
+ * Present only when `--remember` was asked: true when the key was stored beside
1472
+ * license.json, false when the key file could not be written. The service's
1473
+ * proof of the key (an accepted PUT, or the identity check on an already-shared
1474
+ * run) is part of every success, so it is never the reason.
1475
+ */
1476
+ remembered: z8.boolean().optional(),
1477
+ /**
1478
+ * The ISO-8601 instant the share link now dies at — present only when an expiry
1479
+ * policy (`--expiry` or publish.json's `expiry`) was set. Absent means never
1480
+ * expires, exactly like a publish before the feature existed.
1481
+ */
1482
+ expiresAt: z8.string().min(1).optional(),
1483
+ captions: z8.boolean(),
1484
+ chapters: z8.boolean(),
1485
+ /** False is a state, not a fault: no FFmpeg on PATH means no poster frame. */
1486
+ poster: z8.boolean()
1346
1487
  })
1347
1488
  ]).refine(okMatchesProblems, "ok must be true iff problems is empty");
1348
1489