@plaintake/scenario 1.23.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 +486 -415
- package/dist/schema/baseline.d.ts +182 -0
- package/dist/schema/errors.d.ts +1 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/render-plan.d.ts +3 -3
- package/dist/schema/results.d.ts +65 -3
- package/package.json +1 -1
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 =
|
|
27
|
-
var DemoEventTypeSchema =
|
|
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 =
|
|
68
|
-
role:
|
|
69
|
-
accessibleName:
|
|
70
|
-
selector:
|
|
71
|
-
rect:
|
|
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 =
|
|
74
|
-
schema:
|
|
75
|
-
seq:
|
|
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:
|
|
80
|
-
title:
|
|
81
|
-
subtitle:
|
|
139
|
+
stableId: z2.string().min(1).optional(),
|
|
140
|
+
title: z2.string().optional(),
|
|
141
|
+
subtitle: z2.string().optional(),
|
|
82
142
|
target: TargetSchema.optional(),
|
|
83
|
-
payload:
|
|
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:
|
|
151
|
+
actor: z2.string().min(1).optional()
|
|
92
152
|
});
|
|
93
153
|
|
|
94
154
|
// ../schema/src/json-schema.ts
|
|
95
|
-
import { z as
|
|
155
|
+
import { z as z4 } from "zod";
|
|
96
156
|
|
|
97
157
|
// ../schema/src/scenario.ts
|
|
98
|
-
import { z as
|
|
99
|
-
var ScenarioIntroSchema =
|
|
100
|
-
lines:
|
|
101
|
-
narration:
|
|
102
|
-
durationMs:
|
|
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 =
|
|
105
|
-
maxZoom:
|
|
106
|
-
margin:
|
|
107
|
-
easeMs:
|
|
108
|
-
minDwellMs:
|
|
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 =
|
|
111
|
-
speed:
|
|
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:
|
|
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 =
|
|
141
|
-
schema:
|
|
142
|
-
id:
|
|
143
|
-
title:
|
|
144
|
-
language:
|
|
145
|
-
viewport:
|
|
146
|
-
width:
|
|
147
|
-
height:
|
|
148
|
-
deviceScaleFactor:
|
|
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:
|
|
151
|
-
timezoneId:
|
|
152
|
-
colorScheme:
|
|
153
|
-
reducedMotion:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
235
|
-
var
|
|
236
|
-
var BundleManifestSchema =
|
|
237
|
-
schema:
|
|
238
|
-
status:
|
|
239
|
-
scenario:
|
|
240
|
-
toolchain:
|
|
241
|
-
node:
|
|
242
|
-
playwright:
|
|
243
|
-
chromiumRevision:
|
|
244
|
-
ffmpeg:
|
|
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:
|
|
247
|
-
fontSha256:
|
|
248
|
-
containerImage:
|
|
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:
|
|
315
|
+
plainmotion: z5.string().optional()
|
|
256
316
|
}),
|
|
257
|
-
environment:
|
|
258
|
-
os:
|
|
317
|
+
environment: z5.object({
|
|
318
|
+
os: z5.string(),
|
|
259
319
|
/** Part of the reproducibility claim: byte-identity holds per architecture. */
|
|
260
|
-
architecture:
|
|
261
|
-
locale:
|
|
262
|
-
timezone:
|
|
263
|
-
viewport:
|
|
264
|
-
deviceScaleFactor:
|
|
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:
|
|
267
|
-
artifacts:
|
|
268
|
-
|
|
269
|
-
path:
|
|
270
|
-
sha256:
|
|
271
|
-
bytes:
|
|
272
|
-
role:
|
|
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
|
|
338
|
+
import { z as z6 } from "zod";
|
|
279
339
|
var NARRATION_INDEX_SCHEMA = "agent-demo.narration-index/v1";
|
|
280
|
-
var NarrationIndexClipSchema =
|
|
340
|
+
var NarrationIndexClipSchema = z6.object({
|
|
281
341
|
/** The step id, which is also the clip id and the cache filename stem. */
|
|
282
|
-
id:
|
|
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:
|
|
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:
|
|
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:
|
|
359
|
+
voice: z6.string().min(1).optional()
|
|
300
360
|
});
|
|
301
|
-
var NarrationIndexSchema =
|
|
302
|
-
schema:
|
|
303
|
-
speech:
|
|
304
|
-
voice:
|
|
305
|
-
voices:
|
|
306
|
-
speed:
|
|
307
|
-
dtype:
|
|
308
|
-
pronunciations:
|
|
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:
|
|
370
|
+
clips: z6.array(NarrationIndexClipSchema).min(1)
|
|
311
371
|
});
|
|
312
372
|
|
|
313
373
|
// ../schema/src/render-plan.ts
|
|
314
|
-
import { z as
|
|
315
|
-
var CueSchema =
|
|
316
|
-
id:
|
|
317
|
-
startMs:
|
|
318
|
-
endMs:
|
|
319
|
-
lines:
|
|
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 =
|
|
322
|
-
var OutroSchema =
|
|
323
|
-
durationMs:
|
|
324
|
-
lines:
|
|
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:
|
|
387
|
+
assPath: z7.literal("captions/outro.ass")
|
|
328
388
|
});
|
|
329
|
-
var IntroSchema =
|
|
330
|
-
durationMs:
|
|
331
|
-
lines:
|
|
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:
|
|
394
|
+
assPath: z7.literal("captions/intro.ass")
|
|
335
395
|
});
|
|
336
|
-
var ChapterMarkSchema =
|
|
337
|
-
startMs:
|
|
338
|
-
endMs:
|
|
339
|
-
title:
|
|
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 =
|
|
342
|
-
metadataPath:
|
|
343
|
-
marks:
|
|
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 =
|
|
357
|
-
id:
|
|
358
|
-
x:
|
|
359
|
-
y:
|
|
360
|
-
arriveMs:
|
|
361
|
-
departMs:
|
|
362
|
-
action:
|
|
363
|
-
rippleMs:
|
|
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:
|
|
454
|
+
scale: z7.number().int().min(1).max(400).optional()
|
|
395
455
|
});
|
|
396
|
-
var CursorSchema =
|
|
397
|
-
assPath:
|
|
398
|
-
points:
|
|
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 =
|
|
412
|
-
id:
|
|
413
|
-
enterMs:
|
|
414
|
-
holdFromMs:
|
|
415
|
-
x:
|
|
416
|
-
y:
|
|
417
|
-
w:
|
|
418
|
-
h:
|
|
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 =
|
|
421
|
-
commandPath:
|
|
422
|
-
shots:
|
|
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 =
|
|
451
|
-
id:
|
|
452
|
-
label:
|
|
510
|
+
var ActorSchema = z7.object({
|
|
511
|
+
id: z7.string().min(1),
|
|
512
|
+
label: z7.string().min(1)
|
|
453
513
|
});
|
|
454
|
-
var SegmentSchema =
|
|
455
|
-
id:
|
|
456
|
-
actorId:
|
|
457
|
-
startMs:
|
|
458
|
-
endMs:
|
|
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 =
|
|
461
|
-
fromActorId:
|
|
462
|
-
toActorId:
|
|
463
|
-
durationMs:
|
|
464
|
-
lines:
|
|
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:
|
|
527
|
+
assPath: z7.string().regex(/^captions\/turn-[0-9]+\.ass$/)
|
|
468
528
|
});
|
|
469
|
-
var ExplainSegmentPlanSchema =
|
|
470
|
-
id:
|
|
471
|
-
segmentPath:
|
|
472
|
-
durationMs:
|
|
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 =
|
|
475
|
-
id:
|
|
476
|
-
x:
|
|
477
|
-
y:
|
|
478
|
-
width:
|
|
479
|
-
height:
|
|
480
|
-
startMs:
|
|
481
|
-
endMs:
|
|
482
|
-
label:
|
|
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:
|
|
495
|
-
x:
|
|
496
|
-
y:
|
|
497
|
-
width:
|
|
498
|
-
height:
|
|
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:
|
|
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 =
|
|
538
|
-
assPath:
|
|
539
|
-
rects:
|
|
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 =
|
|
550
|
-
id:
|
|
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:
|
|
565
|
-
atMs:
|
|
566
|
-
durationMs:
|
|
567
|
-
source:
|
|
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:
|
|
643
|
+
voice: z7.string().min(1).optional()
|
|
584
644
|
});
|
|
585
|
-
var SpeechEngineSchema =
|
|
586
|
-
name:
|
|
587
|
-
version:
|
|
588
|
-
modelSha256:
|
|
589
|
-
voice:
|
|
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 =
|
|
592
|
-
trackPath:
|
|
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:
|
|
601
|
-
channels:
|
|
602
|
-
clips:
|
|
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 =
|
|
688
|
+
var ThemeSchema = z7.object({ accentColor: HEX_COLOUR });
|
|
629
689
|
var RENDER_PLAN_SCHEMA = "agent-demo.render/v1";
|
|
630
|
-
var RenderPlanSchema =
|
|
631
|
-
schema:
|
|
632
|
-
source:
|
|
633
|
-
path:
|
|
634
|
-
sha256:
|
|
635
|
-
durationMs:
|
|
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:
|
|
698
|
-
width:
|
|
699
|
-
height:
|
|
700
|
-
fps:
|
|
701
|
-
pixelFormat:
|
|
702
|
-
capture:
|
|
703
|
-
letterbox:
|
|
704
|
-
width:
|
|
705
|
-
height:
|
|
706
|
-
x:
|
|
707
|
-
y:
|
|
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:
|
|
731
|
-
tailPadMs:
|
|
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:
|
|
746
|
-
language:
|
|
747
|
-
srtPath:
|
|
748
|
-
vttPath:
|
|
749
|
-
assPath:
|
|
750
|
-
cues:
|
|
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:
|
|
765
|
-
fontFile:
|
|
766
|
-
fontName:
|
|
767
|
-
fontSize:
|
|
768
|
-
textColor:
|
|
769
|
-
outlineColor:
|
|
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:
|
|
772
|
-
marginBottom:
|
|
773
|
-
marginSide:
|
|
774
|
-
borderStyle:
|
|
775
|
-
outlineOpacity:
|
|
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:
|
|
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:
|
|
862
|
+
alignment: z7.union([z7.literal(2), z7.literal(8)]).default(2)
|
|
803
863
|
}),
|
|
804
|
-
ffmpeg:
|
|
805
|
-
base:
|
|
806
|
-
soft:
|
|
807
|
-
hard:
|
|
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(),
|
|
@@ -862,7 +922,7 @@ var RenderPlanSchema = z6.object({
|
|
|
862
922
|
* argument arrays from the plan, and read this field (via `outputOf`) so a re-cut writes
|
|
863
923
|
* the file the original recording wrote rather than the fallback name.
|
|
864
924
|
*/
|
|
865
|
-
outputPath:
|
|
925
|
+
outputPath: z7.string().regex(/^output\/[a-z0-9][a-z0-9-]*\.mp4$/, "must be output/<scenario-id>.mp4, a kebab-case id").optional(),
|
|
866
926
|
/**
|
|
867
927
|
* The capture timeline cut into windows — of a genuine multi-actor cast, of an implicit
|
|
868
928
|
* single actor pausing for explain cut-aways, or both at once. `segments` is the one
|
|
@@ -886,11 +946,11 @@ var RenderPlanSchema = z6.object({
|
|
|
886
946
|
* it out): a lone, unsegmented capture has no gap to claim in the first place, and needs
|
|
887
947
|
* none of these five fields at all.
|
|
888
948
|
*/
|
|
889
|
-
actors:
|
|
890
|
-
segments:
|
|
891
|
-
transitionCards:
|
|
892
|
-
badgesAssPath:
|
|
893
|
-
explainSegments:
|
|
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()
|
|
894
954
|
}).refine(
|
|
895
955
|
(plan) => {
|
|
896
956
|
const castFields = [plan.actors, plan.transitionCards, plan.badgesAssPath];
|
|
@@ -961,95 +1021,95 @@ var RenderPlanSchema = z6.object({
|
|
|
961
1021
|
);
|
|
962
1022
|
|
|
963
1023
|
// ../schema/src/results.ts
|
|
964
|
-
import { z as
|
|
965
|
-
var RELATIVE_POSIX =
|
|
966
|
-
var
|
|
967
|
-
var DISPLAY_PATH =
|
|
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);
|
|
968
1028
|
var envelope = (kind) => ({
|
|
969
|
-
schema:
|
|
970
|
-
kind:
|
|
971
|
-
ok:
|
|
1029
|
+
schema: z8.literal("agent-demo.result/v1"),
|
|
1030
|
+
kind: z8.literal(kind),
|
|
1031
|
+
ok: z8.boolean(),
|
|
972
1032
|
/** Human-readable failures. Empty iff ok. */
|
|
973
|
-
problems:
|
|
1033
|
+
problems: z8.array(z8.string())
|
|
974
1034
|
});
|
|
975
|
-
var ArtifactRefSchema =
|
|
1035
|
+
var ArtifactRefSchema = z8.object({
|
|
976
1036
|
path: RELATIVE_POSIX,
|
|
977
|
-
sha256:
|
|
978
|
-
bytes:
|
|
1037
|
+
sha256: Sha2563,
|
|
1038
|
+
bytes: z8.number().int().nonnegative()
|
|
979
1039
|
});
|
|
980
|
-
var StreamSummarySchema =
|
|
981
|
-
container:
|
|
982
|
-
durationMs:
|
|
983
|
-
video:
|
|
984
|
-
codec:
|
|
985
|
-
width:
|
|
986
|
-
height:
|
|
987
|
-
pixelFormat:
|
|
988
|
-
fps:
|
|
989
|
-
frames:
|
|
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(),
|
|
990
1050
|
/**
|
|
991
1051
|
* The video stream's own duration, as distinct from the container's above. The
|
|
992
1052
|
* constant-rate check measures frames against this, because a padded narration track makes
|
|
993
1053
|
* the container's duration the audio's. Zero for a WebM, which carries no per-track
|
|
994
1054
|
* duration at all.
|
|
995
1055
|
*/
|
|
996
|
-
durationMs:
|
|
1056
|
+
durationMs: z8.number().int().nonnegative()
|
|
997
1057
|
}),
|
|
998
|
-
subtitles:
|
|
1058
|
+
subtitles: z8.array(z8.object({ codec: z8.string(), language: z8.string(), title: z8.string() })),
|
|
999
1059
|
/** Properties, not a count — a narration track muxed in stereo or at the wrong rate
|
|
1000
1060
|
* still counts as one stream. */
|
|
1001
|
-
audio:
|
|
1002
|
-
|
|
1003
|
-
codec:
|
|
1004
|
-
sampleRate:
|
|
1005
|
-
channels:
|
|
1061
|
+
audio: z8.array(
|
|
1062
|
+
z8.object({
|
|
1063
|
+
codec: z8.string(),
|
|
1064
|
+
sampleRate: z8.number().int().nonnegative(),
|
|
1065
|
+
channels: z8.number().int().nonnegative()
|
|
1006
1066
|
})
|
|
1007
1067
|
),
|
|
1008
1068
|
/** Titled chapters make FFmpeg's MP4 muxer add a `bin_data` track beside the `chpl` atom. */
|
|
1009
|
-
dataStreams:
|
|
1010
|
-
chapters:
|
|
1011
|
-
|
|
1012
|
-
startMs:
|
|
1013
|
-
endMs:
|
|
1014
|
-
title:
|
|
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()
|
|
1015
1075
|
})
|
|
1016
1076
|
)
|
|
1017
1077
|
});
|
|
1018
|
-
var ToolchainSchema =
|
|
1019
|
-
node:
|
|
1020
|
-
playwright:
|
|
1021
|
-
chromiumRevision:
|
|
1022
|
-
ffmpeg:
|
|
1078
|
+
var ToolchainSchema = z8.object({
|
|
1079
|
+
node: z8.string(),
|
|
1080
|
+
playwright: z8.string(),
|
|
1081
|
+
chromiumRevision: z8.string(),
|
|
1082
|
+
ffmpeg: z8.string(),
|
|
1023
1083
|
/** May be the literal "unknown" — never fabricated. */
|
|
1024
|
-
libass:
|
|
1025
|
-
fontSha256:
|
|
1084
|
+
libass: z8.string(),
|
|
1085
|
+
fontSha256: Sha2563
|
|
1026
1086
|
});
|
|
1027
|
-
var AssertionSchema =
|
|
1028
|
-
var StatusSchema =
|
|
1029
|
-
var VariantsSchema =
|
|
1030
|
-
var ValidationResultSchema =
|
|
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({
|
|
1031
1091
|
...envelope("validate"),
|
|
1032
1092
|
/** Absent when the scenario could not be loaded at all. */
|
|
1033
|
-
scenario:
|
|
1034
|
-
id:
|
|
1035
|
-
title:
|
|
1036
|
-
language:
|
|
1093
|
+
scenario: z8.object({
|
|
1094
|
+
id: z8.string(),
|
|
1095
|
+
title: z8.string(),
|
|
1096
|
+
language: z8.string(),
|
|
1037
1097
|
/**
|
|
1038
1098
|
* Whether this scenario needs a person, and in which phase. Reported so an author
|
|
1039
1099
|
* — or an agent reading `--json` — learns that a scenario cannot run unattended
|
|
1040
1100
|
* without first trying to run it. Defaulted, so a result written before this parses.
|
|
1041
1101
|
*/
|
|
1042
|
-
handoff:
|
|
1043
|
-
sha256:
|
|
1102
|
+
handoff: z8.enum(["none", "preflight", "session"]).default("none"),
|
|
1103
|
+
sha256: Sha2563
|
|
1044
1104
|
}).optional()
|
|
1045
1105
|
});
|
|
1046
|
-
var RunCommandResultSchema =
|
|
1106
|
+
var RunCommandResultSchema = z8.object({
|
|
1047
1107
|
...envelope("run"),
|
|
1048
1108
|
bundleDir: DISPLAY_PATH,
|
|
1049
1109
|
status: StatusSchema,
|
|
1050
|
-
cueCount:
|
|
1110
|
+
cueCount: z8.number().int().nonnegative(),
|
|
1051
1111
|
/** Zero on the Free Tier, and zero for a scenario with no `chapter` calls. */
|
|
1052
|
-
chapterCount:
|
|
1112
|
+
chapterCount: z8.number().int().nonnegative(),
|
|
1053
1113
|
/**
|
|
1054
1114
|
* How many lines the video speaks. Zero for a silent run, which is the default.
|
|
1055
1115
|
*
|
|
@@ -1058,119 +1118,130 @@ var RunCommandResultSchema = z7.object({
|
|
|
1058
1118
|
* scenario whose steps carry no `subtitle`, which produces a perfectly valid silent video and
|
|
1059
1119
|
* would look like the feature being broken rather than like nothing having been written to say.
|
|
1060
1120
|
*/
|
|
1061
|
-
narratedCount:
|
|
1062
|
-
diagnostics:
|
|
1063
|
-
assertions:
|
|
1064
|
-
outputs:
|
|
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),
|
|
1065
1125
|
/**
|
|
1066
1126
|
* The previous bundle this run replaced, archived as a `.<timestamp>` sibling rather
|
|
1067
1127
|
* than deleted. Absent when nothing occupied the output directory — the common first
|
|
1068
1128
|
* run — or when `--no-archive` asked for the old replace semantics. Optional and
|
|
1069
1129
|
* additive, so every result written before archives existed still parses.
|
|
1070
1130
|
*/
|
|
1071
|
-
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()
|
|
1072
1138
|
});
|
|
1073
|
-
var CheckAssertionSchema = AssertionSchema.extend({ message:
|
|
1074
|
-
var CheckCommandResultSchema =
|
|
1139
|
+
var CheckAssertionSchema = AssertionSchema.extend({ message: z8.string().optional() });
|
|
1140
|
+
var CheckCommandResultSchema = z8.object({
|
|
1075
1141
|
...envelope("check"),
|
|
1076
1142
|
bundleDir: DISPLAY_PATH,
|
|
1077
1143
|
status: StatusSchema,
|
|
1078
|
-
assertions:
|
|
1144
|
+
assertions: z8.array(CheckAssertionSchema),
|
|
1079
1145
|
/**
|
|
1080
1146
|
* Handoff notes, plus a `speech.close` note when closing the narrator failed after
|
|
1081
1147
|
* capture. Cue, chapter, cursor and camera diagnostics are all derived from a render plan
|
|
1082
1148
|
* that `check` never builds — fabricating them would report on capabilities this command
|
|
1083
1149
|
* cannot see.
|
|
1084
1150
|
*/
|
|
1085
|
-
diagnostics:
|
|
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()
|
|
1086
1158
|
});
|
|
1087
|
-
var RenderCommandResultSchema =
|
|
1159
|
+
var RenderCommandResultSchema = z8.object({
|
|
1088
1160
|
...envelope("render"),
|
|
1089
1161
|
bundleDir: DISPLAY_PATH,
|
|
1090
1162
|
variants: VariantsSchema,
|
|
1091
|
-
streams:
|
|
1163
|
+
streams: z8.object({
|
|
1092
1164
|
base: StreamSummarySchema.optional(),
|
|
1093
1165
|
soft: StreamSummarySchema.optional(),
|
|
1094
1166
|
hard: StreamSummarySchema.optional()
|
|
1095
1167
|
}),
|
|
1096
|
-
outputs:
|
|
1168
|
+
outputs: z8.array(ArtifactRefSchema)
|
|
1097
1169
|
});
|
|
1098
|
-
var VerificationReportSchema =
|
|
1170
|
+
var VerificationReportSchema = z8.object({
|
|
1099
1171
|
...envelope("verify"),
|
|
1100
1172
|
bundleDir: DISPLAY_PATH,
|
|
1101
|
-
artifactCount:
|
|
1173
|
+
artifactCount: z8.number().int().nonnegative()
|
|
1102
1174
|
});
|
|
1103
|
-
var WarmCommandResultSchema =
|
|
1175
|
+
var WarmCommandResultSchema = z8.object({
|
|
1104
1176
|
...envelope("warm"),
|
|
1105
1177
|
bundleDir: DISPLAY_PATH,
|
|
1106
|
-
clipCount:
|
|
1107
|
-
synthesizedCount:
|
|
1108
|
-
cachedCount:
|
|
1178
|
+
clipCount: z8.number().int().nonnegative(),
|
|
1179
|
+
synthesizedCount: z8.number().int().nonnegative(),
|
|
1180
|
+
cachedCount: z8.number().int().nonnegative()
|
|
1109
1181
|
});
|
|
1110
|
-
var
|
|
1111
|
-
var DiffCommandResultSchema = z7.object({
|
|
1182
|
+
var DiffCommandResultSchema = z8.object({
|
|
1112
1183
|
...envelope("diff"),
|
|
1113
1184
|
bundleA: DISPLAY_PATH,
|
|
1114
1185
|
bundleB: DISPLAY_PATH,
|
|
1115
|
-
identical:
|
|
1116
|
-
differences:
|
|
1186
|
+
identical: z8.boolean(),
|
|
1187
|
+
differences: z8.array(DifferenceSchema)
|
|
1117
1188
|
});
|
|
1118
|
-
var CompareCommandResultSchema =
|
|
1189
|
+
var CompareCommandResultSchema = z8.object({
|
|
1119
1190
|
...envelope("compare"),
|
|
1120
1191
|
bundleA: DISPLAY_PATH,
|
|
1121
1192
|
bundleB: DISPLAY_PATH,
|
|
1122
|
-
checks:
|
|
1193
|
+
checks: z8.array(z8.object({ name: z8.string(), ok: z8.boolean(), detail: z8.string() }))
|
|
1123
1194
|
});
|
|
1124
|
-
var InitResultSchema =
|
|
1195
|
+
var InitResultSchema = z8.object({
|
|
1125
1196
|
...envelope("init"),
|
|
1126
|
-
files:
|
|
1127
|
-
validates:
|
|
1197
|
+
files: z8.array(DISPLAY_PATH),
|
|
1198
|
+
validates: z8.boolean()
|
|
1128
1199
|
});
|
|
1129
|
-
var PruneCandidateSchema =
|
|
1200
|
+
var PruneCandidateSchema = z8.object({
|
|
1130
1201
|
path: DISPLAY_PATH,
|
|
1131
|
-
bytes:
|
|
1132
|
-
scenarioId:
|
|
1202
|
+
bytes: z8.number().int().nonnegative(),
|
|
1203
|
+
scenarioId: z8.string().optional()
|
|
1133
1204
|
});
|
|
1134
|
-
var PruneCommandResultSchema =
|
|
1205
|
+
var PruneCommandResultSchema = z8.object({
|
|
1135
1206
|
...envelope("prune"),
|
|
1136
|
-
dryRun:
|
|
1207
|
+
dryRun: z8.boolean(),
|
|
1137
1208
|
/** What was removed (confirmed) or would be (dry run) — never includes a `failed` entry. */
|
|
1138
|
-
candidates:
|
|
1209
|
+
candidates: z8.array(PruneCandidateSchema),
|
|
1139
1210
|
/** A discovered bundle that was never attempted, and why — never silently dropped. */
|
|
1140
|
-
skipped:
|
|
1211
|
+
skipped: z8.array(z8.object({ path: DISPLAY_PATH, reason: z8.string() })),
|
|
1141
1212
|
/** A selected candidate `rm` was attempted on and could not remove, and why. Always empty
|
|
1142
1213
|
* on a dry run, since nothing is attempted until `--yes`. */
|
|
1143
|
-
failed:
|
|
1214
|
+
failed: z8.array(z8.object({ path: DISPLAY_PATH, reason: z8.string() })),
|
|
1144
1215
|
/** What `candidates` account for: bytes actually reclaimed if `dryRun` is false, or would
|
|
1145
1216
|
* be reclaimed if every candidate succeeded. */
|
|
1146
|
-
bytesReclaimed:
|
|
1217
|
+
bytesReclaimed: z8.number().int().nonnegative()
|
|
1147
1218
|
});
|
|
1148
|
-
var ImportCommandResultSchema =
|
|
1219
|
+
var ImportCommandResultSchema = z8.object({
|
|
1149
1220
|
...envelope("import"),
|
|
1150
1221
|
tracePath: DISPLAY_PATH,
|
|
1151
1222
|
outputPath: DISPLAY_PATH,
|
|
1152
1223
|
/** How many `demo.step` calls the draft carries. Zero only on the failure path. */
|
|
1153
|
-
stepCount:
|
|
1224
|
+
stepCount: z8.number().int().nonnegative(),
|
|
1154
1225
|
/** Whether the written draft already passes `plaintake validate`. Best-effort: a draft
|
|
1155
1226
|
* that fails is still written, still a success, and its problems are warnings here. */
|
|
1156
|
-
validates:
|
|
1227
|
+
validates: z8.boolean(),
|
|
1157
1228
|
/** Author-facing advice: multi-page traces, redactions, unimported trace calls, and the
|
|
1158
1229
|
* standing review-for-secrets line. Never empty on success. */
|
|
1159
|
-
warnings:
|
|
1230
|
+
warnings: z8.array(z8.string())
|
|
1160
1231
|
});
|
|
1161
|
-
var InspectResultSchema =
|
|
1232
|
+
var InspectResultSchema = z8.object({
|
|
1162
1233
|
...envelope("inspect"),
|
|
1163
1234
|
bundleDir: DISPLAY_PATH,
|
|
1164
1235
|
status: StatusSchema,
|
|
1165
|
-
scenario:
|
|
1236
|
+
scenario: z8.object({ id: z8.string(), sourceSha256: Sha2563 }),
|
|
1166
1237
|
toolchain: ToolchainSchema,
|
|
1167
|
-
video:
|
|
1238
|
+
video: z8.object({
|
|
1168
1239
|
/** The whole output: the cards, if any, plus the content between them. */
|
|
1169
|
-
durationMs:
|
|
1170
|
-
frames:
|
|
1171
|
-
width:
|
|
1172
|
-
height:
|
|
1173
|
-
fps:
|
|
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(),
|
|
1174
1245
|
/**
|
|
1175
1246
|
* Split out because `durationMs` alone is misleading once a card exists: 12,034ms of
|
|
1176
1247
|
* output can be 9,034ms of recording. The parts are reported separately rather than
|
|
@@ -1185,20 +1256,20 @@ var InspectResultSchema = z7.object({
|
|
|
1185
1256
|
* the word means, and is worth stating because the plan's own `video.durationMs` is a
|
|
1186
1257
|
* different number that includes the opening card.
|
|
1187
1258
|
*/
|
|
1188
|
-
contentDurationMs:
|
|
1189
|
-
introDurationMs:
|
|
1190
|
-
outroDurationMs:
|
|
1259
|
+
contentDurationMs: z8.number().int().nonnegative(),
|
|
1260
|
+
introDurationMs: z8.number().int().nonnegative().default(0),
|
|
1261
|
+
outroDurationMs: z8.number().int().nonnegative()
|
|
1191
1262
|
}),
|
|
1192
|
-
captions:
|
|
1193
|
-
language:
|
|
1194
|
-
cueCount:
|
|
1195
|
-
characterCount:
|
|
1196
|
-
medianCueMs:
|
|
1197
|
-
shortestCueMs:
|
|
1198
|
-
longestCueMs:
|
|
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()
|
|
1199
1270
|
}),
|
|
1200
1271
|
/** Measured from the rendered MP4 where possible, declared from the plan otherwise. */
|
|
1201
|
-
chapters:
|
|
1272
|
+
chapters: z8.array(z8.object({ startMs: z8.number().int().nonnegative(), title: z8.string() })),
|
|
1202
1273
|
/**
|
|
1203
1274
|
* The narration this bundle carries, or absent for a silent one.
|
|
1204
1275
|
*
|
|
@@ -1209,13 +1280,13 @@ var InspectResultSchema = z7.object({
|
|
|
1209
1280
|
* model behind it, and inventing a voice id for it would make the one field that answers "why
|
|
1210
1281
|
* does this sound different" answer wrongly.
|
|
1211
1282
|
*/
|
|
1212
|
-
narration:
|
|
1213
|
-
clipCount:
|
|
1283
|
+
narration: z8.object({
|
|
1284
|
+
clipCount: z8.number().int().positive(),
|
|
1214
1285
|
/** Total speech, excluding the silence between clips. Not the video's length. */
|
|
1215
|
-
spokenMs:
|
|
1286
|
+
spokenMs: z8.number().int().nonnegative(),
|
|
1216
1287
|
/** How many clips the author supplied rather than the model producing. */
|
|
1217
|
-
suppliedCount:
|
|
1218
|
-
voice:
|
|
1288
|
+
suppliedCount: z8.number().int().nonnegative(),
|
|
1289
|
+
voice: z8.string().optional()
|
|
1219
1290
|
}).optional(),
|
|
1220
1291
|
/**
|
|
1221
1292
|
* The multi-actor demo's cast, or absent for a single-actor bundle — every bundle
|
|
@@ -1232,19 +1303,19 @@ var InspectResultSchema = z7.object({
|
|
|
1232
1303
|
* the field a reader of this JSON would already trust as the timeline, not from a second
|
|
1233
1304
|
* field that only agrees with it by a schema-level invariant this result does not surface.
|
|
1234
1305
|
*/
|
|
1235
|
-
actors:
|
|
1306
|
+
actors: z8.object({
|
|
1236
1307
|
/** `plan.actors.length`. Reported alongside `labels` rather than left for a caller to
|
|
1237
1308
|
* compute, matching `narration.clipCount` sitting beside `narration` above for the
|
|
1238
1309
|
* same reason: the count is the fact most callers want first, before the list. */
|
|
1239
|
-
count:
|
|
1310
|
+
count: z8.number().int().min(2),
|
|
1240
1311
|
/** In `plan.actors` order — the cast list a badge or transition card would draw, not
|
|
1241
1312
|
* deduplicated or sorted. */
|
|
1242
|
-
labels:
|
|
1313
|
+
labels: z8.array(z8.string().min(1)).min(2),
|
|
1243
1314
|
/** Hand-offs, not participants: one fewer than `plan.segments.length`, since a
|
|
1244
1315
|
* segment is a window one actor already holds and a turn is the cut between two of
|
|
1245
1316
|
* them — the same count `plan.transitionCards.length` carries, arrived at
|
|
1246
1317
|
* independently from `segments` for the reason above. */
|
|
1247
|
-
turnCount:
|
|
1318
|
+
turnCount: z8.number().int().nonnegative()
|
|
1248
1319
|
}).refine((value) => value.count === value.labels.length, "actors.count must equal actors.labels.length").optional(),
|
|
1249
1320
|
/**
|
|
1250
1321
|
* The explain scenes this bundle cut away to, in composite order, or absent for a bundle
|
|
@@ -1256,20 +1327,20 @@ var InspectResultSchema = z7.object({
|
|
|
1256
1327
|
* `actors` is sourced from the plan rather than re-read from `demo.explain()` calls: a
|
|
1257
1328
|
* bundle whose scenario has since been edited or deleted still inspects correctly.
|
|
1258
1329
|
*/
|
|
1259
|
-
explainScenes:
|
|
1260
|
-
|
|
1261
|
-
id:
|
|
1262
|
-
durationMs:
|
|
1330
|
+
explainScenes: z8.array(
|
|
1331
|
+
z8.object({
|
|
1332
|
+
id: z8.string().min(1),
|
|
1333
|
+
durationMs: z8.number().int().positive()
|
|
1263
1334
|
})
|
|
1264
1335
|
).min(1).optional(),
|
|
1265
1336
|
/** The rendered MP4s, from the manifest, so hashes are not recomputed. */
|
|
1266
|
-
outputs:
|
|
1267
|
-
assertions:
|
|
1268
|
-
artifactCount:
|
|
1337
|
+
outputs: z8.array(ArtifactRefSchema),
|
|
1338
|
+
assertions: z8.array(AssertionSchema),
|
|
1339
|
+
artifactCount: z8.number().int().nonnegative(),
|
|
1269
1340
|
/** Everything the manifest accounts for. What "delete this bundle" would reclaim. */
|
|
1270
|
-
bundleBytes:
|
|
1341
|
+
bundleBytes: z8.number().int().nonnegative()
|
|
1271
1342
|
});
|
|
1272
|
-
var DoctorResultSchema =
|
|
1343
|
+
var DoctorResultSchema = z8.object({
|
|
1273
1344
|
...envelope("doctor"),
|
|
1274
1345
|
/**
|
|
1275
1346
|
* The PlainTake build this is. Here rather than in `ToolchainSchema` on purpose:
|
|
@@ -1277,12 +1348,12 @@ var DoctorResultSchema = z7.object({
|
|
|
1277
1348
|
* manifest on every release and break the byte-for-byte golden bundle. `doctor` is the
|
|
1278
1349
|
* one command a bug report is asked for, which makes it the right place.
|
|
1279
1350
|
*/
|
|
1280
|
-
version:
|
|
1281
|
-
ffmpeg:
|
|
1282
|
-
ffprobe:
|
|
1283
|
-
libass:
|
|
1284
|
-
hasLibass:
|
|
1285
|
-
hasX264:
|
|
1351
|
+
version: z8.string(),
|
|
1352
|
+
ffmpeg: z8.string(),
|
|
1353
|
+
ffprobe: z8.string(),
|
|
1354
|
+
libass: z8.string(),
|
|
1355
|
+
hasLibass: z8.boolean(),
|
|
1356
|
+
hasX264: z8.boolean(),
|
|
1286
1357
|
/**
|
|
1287
1358
|
* Whether this FFmpeg can encode AAC, which is what a narrated bundle is muxed with.
|
|
1288
1359
|
*
|
|
@@ -1292,8 +1363,8 @@ var DoctorResultSchema = z7.object({
|
|
|
1292
1363
|
* that actually needs it, with a fix string. Failing `doctor` here would be telling someone
|
|
1293
1364
|
* their installation is broken for a capability they may never turn on.
|
|
1294
1365
|
*/
|
|
1295
|
-
hasAac:
|
|
1296
|
-
filters:
|
|
1366
|
+
hasAac: z8.boolean(),
|
|
1367
|
+
filters: z8.array(z8.string()),
|
|
1297
1368
|
/**
|
|
1298
1369
|
* Whether spoken narration could be produced right now, and what is missing if not.
|
|
1299
1370
|
*
|
|
@@ -1302,12 +1373,12 @@ var DoctorResultSchema = z7.object({
|
|
|
1302
1373
|
* `openNarrator` asks before a run, answered by the same function (`speechAssetProblems`), so
|
|
1303
1374
|
* `doctor` cannot say speech is fine and then a run refuse it.
|
|
1304
1375
|
*/
|
|
1305
|
-
speech:
|
|
1306
|
-
ready:
|
|
1376
|
+
speech: z8.object({
|
|
1377
|
+
ready: z8.boolean(),
|
|
1307
1378
|
/** Installed voices, in catalogue order. Empty means the model cannot speak yet. */
|
|
1308
|
-
voices:
|
|
1379
|
+
voices: z8.array(z8.string()),
|
|
1309
1380
|
/** What is missing, in the same words a refused run would use. Empty when ready. */
|
|
1310
|
-
notes:
|
|
1381
|
+
notes: z8.array(z8.string())
|
|
1311
1382
|
}),
|
|
1312
1383
|
/**
|
|
1313
1384
|
* Whether the plainmotion CLI is on `PATH`, for a scenario that wants to call
|
|
@@ -1318,16 +1389,16 @@ var DoctorResultSchema = z7.object({
|
|
|
1318
1389
|
* probe a run's own first `demo.explain()` pays, so `doctor` cannot say plainmotion is fine
|
|
1319
1390
|
* and then a run refuse it.
|
|
1320
1391
|
*/
|
|
1321
|
-
plainmotion:
|
|
1322
|
-
installed:
|
|
1392
|
+
plainmotion: z8.object({
|
|
1393
|
+
installed: z8.boolean(),
|
|
1323
1394
|
/** plainmotion's own `--version` output. Absent when not installed. */
|
|
1324
|
-
version:
|
|
1395
|
+
version: z8.string().optional(),
|
|
1325
1396
|
/** Why the probe failed, in the same words a run's own refusal would use. Absent when installed. */
|
|
1326
|
-
note:
|
|
1397
|
+
note: z8.string().optional()
|
|
1327
1398
|
})
|
|
1328
1399
|
});
|
|
1329
1400
|
var okMatchesProblems = (value) => value.ok === (value.problems.length === 0);
|
|
1330
|
-
var ActivateReasonSchema =
|
|
1401
|
+
var ActivateReasonSchema = z8.enum([
|
|
1331
1402
|
"empty-key",
|
|
1332
1403
|
"not-found",
|
|
1333
1404
|
"revoked",
|
|
@@ -1335,16 +1406,16 @@ var ActivateReasonSchema = z7.enum([
|
|
|
1335
1406
|
"unexpected-response",
|
|
1336
1407
|
"not-configured"
|
|
1337
1408
|
]);
|
|
1338
|
-
var ActivateResultSchema =
|
|
1409
|
+
var ActivateResultSchema = z8.object({
|
|
1339
1410
|
...envelope("activate"),
|
|
1340
1411
|
/** Present only when ok is false, so a machine reader can distinguish the classes. */
|
|
1341
1412
|
reason: ActivateReasonSchema.optional(),
|
|
1342
1413
|
/** Present only on success — a failed activation has no record. The key itself is
|
|
1343
1414
|
* deliberately absent: the caller already has it, and stdout may be logged. */
|
|
1344
|
-
licence:
|
|
1345
|
-
subject:
|
|
1346
|
-
activatedAt:
|
|
1347
|
-
uses:
|
|
1415
|
+
licence: z8.object({
|
|
1416
|
+
subject: z8.string().min(1),
|
|
1417
|
+
activatedAt: z8.string().min(1),
|
|
1418
|
+
uses: z8.number().int().nonnegative()
|
|
1348
1419
|
}).optional()
|
|
1349
1420
|
}).refine(okMatchesProblems, "ok must be true iff problems is empty").refine(
|
|
1350
1421
|
// Making the "present only when" comments above literally true, so a reader can
|
|
@@ -1352,25 +1423,25 @@ var ActivateResultSchema = z7.object({
|
|
|
1352
1423
|
(value) => value.reason !== void 0 === !value.ok && (value.licence === void 0 || value.ok),
|
|
1353
1424
|
"reason appears iff ok is false, and licence only on success"
|
|
1354
1425
|
);
|
|
1355
|
-
var LicenceResultSchema =
|
|
1356
|
-
|
|
1426
|
+
var LicenceResultSchema = z8.discriminatedUnion("tier", [
|
|
1427
|
+
z8.object({
|
|
1357
1428
|
...envelope("licence"),
|
|
1358
|
-
tier:
|
|
1429
|
+
tier: z8.literal("free"),
|
|
1359
1430
|
/**
|
|
1360
1431
|
* Why a present-but-unusable record degraded to free, in readLicense's own words.
|
|
1361
1432
|
* Must never quote a filesystem path: assertNoAbsolutePaths exempts only `problems`.
|
|
1362
1433
|
*/
|
|
1363
|
-
problem:
|
|
1434
|
+
problem: z8.string().optional()
|
|
1364
1435
|
}),
|
|
1365
|
-
|
|
1436
|
+
z8.object({
|
|
1366
1437
|
...envelope("licence"),
|
|
1367
|
-
tier:
|
|
1368
|
-
subject:
|
|
1369
|
-
activatedAt:
|
|
1370
|
-
uses:
|
|
1438
|
+
tier: z8.literal("pro"),
|
|
1439
|
+
subject: z8.string().min(1),
|
|
1440
|
+
activatedAt: z8.string().min(1),
|
|
1441
|
+
uses: z8.number().int().nonnegative()
|
|
1371
1442
|
})
|
|
1372
1443
|
]).refine(okMatchesProblems, "ok must be true iff problems is empty");
|
|
1373
|
-
var PublishReasonSchema =
|
|
1444
|
+
var PublishReasonSchema = z8.enum([
|
|
1374
1445
|
"no-endpoint",
|
|
1375
1446
|
"no-key",
|
|
1376
1447
|
"invalid-store",
|
|
@@ -1382,37 +1453,37 @@ var PublishReasonSchema = z7.enum([
|
|
|
1382
1453
|
"conflict",
|
|
1383
1454
|
"unexpected-response"
|
|
1384
1455
|
]);
|
|
1385
|
-
var PublishResultSchema =
|
|
1386
|
-
|
|
1456
|
+
var PublishResultSchema = z8.discriminatedUnion("ok", [
|
|
1457
|
+
z8.object({
|
|
1387
1458
|
...envelope("publish"),
|
|
1388
|
-
ok:
|
|
1459
|
+
ok: z8.literal(false),
|
|
1389
1460
|
reason: PublishReasonSchema
|
|
1390
1461
|
}),
|
|
1391
|
-
|
|
1462
|
+
z8.object({
|
|
1392
1463
|
...envelope("publish"),
|
|
1393
|
-
ok:
|
|
1394
|
-
videoId:
|
|
1464
|
+
ok: z8.literal(true),
|
|
1465
|
+
videoId: z8.string().regex(/^[a-z2-7]{26}$/, "must be a 26-char base32 share id"),
|
|
1395
1466
|
/** The page viewers open — the endpoint plus the id. */
|
|
1396
|
-
url:
|
|
1467
|
+
url: z8.string().regex(/^https?:\/\/\S+$/, "must be an http or https URL"),
|
|
1397
1468
|
/** False when the video was already on the service — nothing was uploaded. */
|
|
1398
|
-
uploaded:
|
|
1469
|
+
uploaded: z8.boolean(),
|
|
1399
1470
|
/**
|
|
1400
1471
|
* Present only when `--remember` was asked: true when the key was stored beside
|
|
1401
1472
|
* license.json, false when the key file could not be written. The service's
|
|
1402
1473
|
* proof of the key (an accepted PUT, or the identity check on an already-shared
|
|
1403
1474
|
* run) is part of every success, so it is never the reason.
|
|
1404
1475
|
*/
|
|
1405
|
-
remembered:
|
|
1476
|
+
remembered: z8.boolean().optional(),
|
|
1406
1477
|
/**
|
|
1407
1478
|
* The ISO-8601 instant the share link now dies at — present only when an expiry
|
|
1408
1479
|
* policy (`--expiry` or publish.json's `expiry`) was set. Absent means never
|
|
1409
1480
|
* expires, exactly like a publish before the feature existed.
|
|
1410
1481
|
*/
|
|
1411
|
-
expiresAt:
|
|
1412
|
-
captions:
|
|
1413
|
-
chapters:
|
|
1482
|
+
expiresAt: z8.string().min(1).optional(),
|
|
1483
|
+
captions: z8.boolean(),
|
|
1484
|
+
chapters: z8.boolean(),
|
|
1414
1485
|
/** False is a state, not a fault: no FFmpeg on PATH means no poster frame. */
|
|
1415
|
-
poster:
|
|
1486
|
+
poster: z8.boolean()
|
|
1416
1487
|
})
|
|
1417
1488
|
]).refine(okMatchesProblems, "ok must be true iff problems is empty");
|
|
1418
1489
|
|