gooseworks 0.5.2 → 0.5.3
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/kit/core/paths.d.ts +2 -0
- package/dist/kit/core/paths.d.ts.map +1 -1
- package/dist/kit/core/paths.js +1 -0
- package/dist/kit/core/paths.js.map +1 -1
- package/dist/kit/core/pieces.d.ts +6 -1
- package/dist/kit/core/pieces.d.ts.map +1 -1
- package/dist/kit/core/pieces.js +17 -8
- package/dist/kit/core/pieces.js.map +1 -1
- package/dist/kit/core/plain-words.d.ts +7 -0
- package/dist/kit/core/plain-words.d.ts.map +1 -0
- package/dist/kit/core/plain-words.js +58 -0
- package/dist/kit/core/plain-words.js.map +1 -0
- package/dist/kit/core/progress-reporter.d.ts +22 -2
- package/dist/kit/core/progress-reporter.d.ts.map +1 -1
- package/dist/kit/core/progress-reporter.js +66 -2
- package/dist/kit/core/progress-reporter.js.map +1 -1
- package/dist/kit/core/run.d.ts.map +1 -1
- package/dist/kit/core/run.js +218 -36
- package/dist/kit/core/run.js.map +1 -1
- package/dist/kit/core/style.d.ts +5 -2
- package/dist/kit/core/style.d.ts.map +1 -1
- package/dist/kit/core/style.js +18 -19
- package/dist/kit/core/style.js.map +1 -1
- package/dist/kit/core/toolchain.d.ts +6 -0
- package/dist/kit/core/toolchain.d.ts.map +1 -1
- package/dist/kit/core/toolchain.js +76 -5
- package/dist/kit/core/toolchain.js.map +1 -1
- package/dist/kit/line/types.d.ts +8 -0
- package/dist/kit/line/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/skills/manifest.json +1 -1
package/dist/kit/core/run.js
CHANGED
|
@@ -60,6 +60,7 @@ const errors_1 = require("./errors");
|
|
|
60
60
|
const files_1 = require("./files");
|
|
61
61
|
const paths_1 = require("./paths");
|
|
62
62
|
const pieces_1 = require("./pieces");
|
|
63
|
+
const plain_words_1 = require("./plain-words");
|
|
63
64
|
const progress_1 = require("./progress");
|
|
64
65
|
const progress_reporter_1 = require("./progress-reporter");
|
|
65
66
|
const save_1 = require("./save");
|
|
@@ -81,11 +82,46 @@ const ASPECT_SIZE = {
|
|
|
81
82
|
'4:5': { width: 1080, height: 1350 },
|
|
82
83
|
'16:9': { width: 1920, height: 1080 },
|
|
83
84
|
};
|
|
85
|
+
const CHECK_FAILED = 'The video didn’t pass the final check.';
|
|
86
|
+
/** For a failure that repeats on every run of the same plan. */
|
|
87
|
+
const PLAN_CHANGE = 'This video can’t be made from this plan. Change the plan, then make it again.';
|
|
88
|
+
/** The server's `failure.code` shape. */
|
|
89
|
+
const FAILURE_CODE = /^[a-z][a-z0-9_]{0,39}$/;
|
|
90
|
+
/** Codes the server keeps the video in Making for (gooseworks-app video-line/run-failure.ts RESUMABLE_CODES). */
|
|
91
|
+
const RESUMABLE_CODES = new Set(['timeout', 'tool_failed', 'needs_missing']);
|
|
92
|
+
/** A part that would not load, in plain words: which part and why go to the log. */
|
|
93
|
+
function loadStop(error) {
|
|
94
|
+
const code = error instanceof loader_1.PartLoadError ? error.code : undefined;
|
|
95
|
+
switch (code) {
|
|
96
|
+
case 'kit_range':
|
|
97
|
+
return new errors_1.KitStop('This video needs a newer video kit. Nothing was spent.', 'update_kit', code);
|
|
98
|
+
case 'unreachable':
|
|
99
|
+
return new errors_1.KitStop('This video’s parts could not be downloaded right now. Run the same command again in a minute. Nothing was spent.', 'stop', code);
|
|
100
|
+
case 'bad_cache':
|
|
101
|
+
return new errors_1.KitStop('The video kit’s folder on this computer is busy or can’t be used. Run the same command again in a minute. Nothing was spent.', 'stop', code);
|
|
102
|
+
case 'withdrawn':
|
|
103
|
+
return new errors_1.KitStop(`A part this video uses was withdrawn. ${PLAN_CHANGE} Nothing was spent.`, 'change_request', code);
|
|
104
|
+
default:
|
|
105
|
+
return new errors_1.KitStop(`A part this video uses can’t run. ${PLAN_CHANGE} Nothing was spent.`, 'change_request', code);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
84
108
|
function lineWords(error) {
|
|
85
109
|
return [error.message, error.fix].filter(Boolean).join(' ');
|
|
86
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* The code the line hears for a step's last failure. A provider failure a later run can get past
|
|
113
|
+
* (its piece has an attempt left, or no piece is named) is reported as `tool_failed`, which keeps
|
|
114
|
+
* the video in Making; the line ends the video on `provider_failed`.
|
|
115
|
+
*/
|
|
116
|
+
function reportedCode(error) {
|
|
117
|
+
if (error.code !== 'provider_failed')
|
|
118
|
+
return error.code;
|
|
119
|
+
return error instanceof pieces_1.PieceFailure && error.spent ? 'provider_failed' : 'tool_failed';
|
|
120
|
+
}
|
|
87
121
|
/** Plain words for a part's failure: the line's own words when it said them, never a part's detail. */
|
|
88
122
|
function failureWords(error) {
|
|
123
|
+
if (error instanceof pieces_1.PieceFailure && error.spent)
|
|
124
|
+
return pieces_1.FAILED_TWICE;
|
|
89
125
|
if (error instanceof pieces_1.PieceFailure)
|
|
90
126
|
return [error.line.error, error.line.fix].filter(Boolean).join(' ');
|
|
91
127
|
switch (error.code) {
|
|
@@ -97,6 +133,9 @@ function failureWords(error) {
|
|
|
97
133
|
return 'This computer is missing something this video needs. Run the computer check, then make it again.';
|
|
98
134
|
case 'timeout':
|
|
99
135
|
return 'Part of this video took too long. Run the same command again; what was made so far is kept.';
|
|
136
|
+
case 'bad_input':
|
|
137
|
+
case 'output_invalid':
|
|
138
|
+
return PLAN_CHANGE;
|
|
100
139
|
default:
|
|
101
140
|
return 'Part of this video could not be made. Run the same command again; what was made so far is kept.';
|
|
102
141
|
}
|
|
@@ -169,6 +208,9 @@ class Maker {
|
|
|
169
208
|
parts = new Map();
|
|
170
209
|
hosted = new Map();
|
|
171
210
|
pieceLocks = new Map();
|
|
211
|
+
/** Where the run is, for the failure report: a step id or a stage before or after the steps. */
|
|
212
|
+
at = 'hand-over';
|
|
213
|
+
failureSent = false;
|
|
172
214
|
constructor(videoId, deps) {
|
|
173
215
|
this.videoId = videoId;
|
|
174
216
|
this.deps = deps;
|
|
@@ -224,6 +266,11 @@ class Maker {
|
|
|
224
266
|
this.store = new save_1.RunStore(this.layout);
|
|
225
267
|
this.cache = new pieces_1.PieceCache(this.layout.pieces);
|
|
226
268
|
this.reporter = new progress_reporter_1.ProgressReporter(deps.line, this.videoId, this.book, deps.log, (message, reason, code) => this.halt(new errors_1.KitStop(message, reason, code)), deps.heartbeatMs, this.stop.signal);
|
|
269
|
+
const replayed = await this.replayPending();
|
|
270
|
+
if (replayed)
|
|
271
|
+
return replayed;
|
|
272
|
+
if (this.fatal)
|
|
273
|
+
throw this.fatal;
|
|
227
274
|
// Reports start now, so checking the style and parts never looks quiet.
|
|
228
275
|
this.reporter.start();
|
|
229
276
|
void this.reporter.send(true);
|
|
@@ -235,9 +282,71 @@ class Maker {
|
|
|
235
282
|
finally {
|
|
236
283
|
this.reporter?.stopTimer();
|
|
237
284
|
await this.reporter?.flush().catch(() => undefined);
|
|
285
|
+
await this.keepPendingReport().catch((error) => deps.log.write('warn', 'the failure report could not be saved', { error: error instanceof Error ? error.message : String(error) }));
|
|
238
286
|
await release();
|
|
239
287
|
}
|
|
240
288
|
}
|
|
289
|
+
reportKey() {
|
|
290
|
+
try {
|
|
291
|
+
return {
|
|
292
|
+
quote_id: this.handed.quote_id,
|
|
293
|
+
plan_sha256: (0, canonical_1.canonicalHash)((0, secrets_1.withoutSignedLinks)(this.handed.plan)),
|
|
294
|
+
lock_sha256: this.handed.parts_lock ? (0, canonical_1.canonicalHash)(this.handed.parts_lock) : '',
|
|
295
|
+
kit: version_1.KIT_VERSION,
|
|
296
|
+
toolchain: this.toolchain,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* An earlier run's failure report the line never took goes before anything else, when it still
|
|
305
|
+
* stands: the same plan, parts, kit and tools, and a code no new run gets past. Otherwise this
|
|
306
|
+
* run reports for itself.
|
|
307
|
+
*/
|
|
308
|
+
async replayPending() {
|
|
309
|
+
const saved = await (0, save_1.readJson)(this.layout.pendingReport);
|
|
310
|
+
if (!saved)
|
|
311
|
+
return null;
|
|
312
|
+
const key = this.reportKey();
|
|
313
|
+
const stands = saved.report === 1 &&
|
|
314
|
+
!!saved.failure &&
|
|
315
|
+
typeof saved.failure.code === 'string' &&
|
|
316
|
+
typeof saved.failure.detail === 'string' &&
|
|
317
|
+
!RESUMABLE_CODES.has(saved.failure.code) &&
|
|
318
|
+
!!key &&
|
|
319
|
+
Object.keys(key).every((field) => saved[field] === key[field]);
|
|
320
|
+
if (!stands) {
|
|
321
|
+
this.deps.log.write('info', 'an earlier run’s failure report no longer stands', { code: saved.failure?.code ?? null });
|
|
322
|
+
await (0, promises_1.rm)(this.layout.pendingReport, { force: true });
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const sent = await this.reporter.replay(saved.failure);
|
|
326
|
+
this.deps.log.write(sent === 'taken' ? 'info' : 'warn', 'an earlier run’s failure report', { sent, step: saved.failure.step, code: saved.failure.code });
|
|
327
|
+
if (sent === 'unsent')
|
|
328
|
+
return null;
|
|
329
|
+
await (0, promises_1.rm)(this.layout.pendingReport, { force: true });
|
|
330
|
+
if (sent === 'refused')
|
|
331
|
+
return null;
|
|
332
|
+
this.failureSent = true;
|
|
333
|
+
return { status: 'failed', message: saved.failure.detail };
|
|
334
|
+
}
|
|
335
|
+
/** Keeps a failure report the line never took for the next run; one taken, or a finished video, clears it. */
|
|
336
|
+
async keepPendingReport() {
|
|
337
|
+
if (!this.reporter || !this.layout)
|
|
338
|
+
return;
|
|
339
|
+
const unsent = this.reporter.undelivered;
|
|
340
|
+
const key = unsent ? this.reportKey() : null;
|
|
341
|
+
if (unsent && key) {
|
|
342
|
+
const pending = { report: 1, ...key, failure: unsent, saved_at: this.now().toISOString() };
|
|
343
|
+
await (0, save_1.writeJson)(this.layout.pendingReport, pending);
|
|
344
|
+
this.deps.log.write('warn', 'the failure report was not taken; the next run sends it', { step: unsent.step, code: unsent.code });
|
|
345
|
+
}
|
|
346
|
+
else if (this.failureSent || this.run?.status === 'done') {
|
|
347
|
+
await (0, promises_1.rm)(this.layout.pendingReport, { force: true });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
241
350
|
devFolders() {
|
|
242
351
|
const parts = this.deps.env[env_1.KIT_DEV_ENV.partsDir] || null;
|
|
243
352
|
const styles = this.deps.env[env_1.KIT_DEV_ENV.stylesDir] || null;
|
|
@@ -254,13 +363,28 @@ class Maker {
|
|
|
254
363
|
this.run.status = stop && stop.reason === 'stop' ? 'stopped' : 'failed';
|
|
255
364
|
await this.saveRun().catch(() => undefined);
|
|
256
365
|
}
|
|
366
|
+
if (stop && (stop.reason === 'failed' || stop.reason === 'change_request' || stop.reason === 'refused')) {
|
|
367
|
+
this.reportFailure(this.at, stop.code ?? 'change_request', stop.message);
|
|
368
|
+
await this.reporter?.flush().catch(() => undefined);
|
|
369
|
+
}
|
|
257
370
|
if (stop) {
|
|
258
371
|
if (stop.reason === 'update_kit')
|
|
259
372
|
return { status: 'update_kit', message: stop.message };
|
|
260
373
|
return { status: stop.reason === 'stop' ? 'stopped' : 'failed', message: stop.message };
|
|
261
374
|
}
|
|
262
|
-
this.deps.log.write('error', 'the run ended on an error', { error: error instanceof Error ? error.message : String(error) });
|
|
263
|
-
|
|
375
|
+
this.deps.log.write('error', 'the run ended on an error', { step: this.at, error: error instanceof Error ? error.message : String(error) });
|
|
376
|
+
const message = 'This video could not be made right now. Run the same command again; what was made so far is kept.';
|
|
377
|
+
// Not a step's failure (a download or a disk write, say): a new run may get past it.
|
|
378
|
+
this.reportFailure(this.at, 'tool_failed', message);
|
|
379
|
+
await this.reporter?.flush().catch(() => undefined);
|
|
380
|
+
return { status: 'failed', message };
|
|
381
|
+
}
|
|
382
|
+
/** Tells the line once why the run gave up; the line's own refusals already ended it on its words. */
|
|
383
|
+
reportFailure(step, code, detail) {
|
|
384
|
+
if (!this.reporter || this.failureSent || this.reporter.refused)
|
|
385
|
+
return;
|
|
386
|
+
this.failureSent = true;
|
|
387
|
+
void this.reporter.fail({ step: step.slice(0, 60), code: FAILURE_CODE.test(code) ? code : 'change_request', detail: detail.trim().slice(0, 2000) });
|
|
264
388
|
}
|
|
265
389
|
async saveRun() {
|
|
266
390
|
this.run.updated_at = this.now().toISOString();
|
|
@@ -269,6 +393,7 @@ class Maker {
|
|
|
269
393
|
async makeHandedOver(dev) {
|
|
270
394
|
const { deps, handed } = this;
|
|
271
395
|
const plan = handed.plan;
|
|
396
|
+
this.at = 'plan';
|
|
272
397
|
if (!plan || plan.project_id !== this.videoId || plan.style_id !== handed.style_package?.style_id || plan.style_version !== handed.style_package?.version) {
|
|
273
398
|
throw new errors_1.KitStop('The plan handed over is not this video’s. Nothing was spent.', 'change_request');
|
|
274
399
|
}
|
|
@@ -308,11 +433,14 @@ class Maker {
|
|
|
308
433
|
throw new errors_1.KitStop('The style handed over is not the one approved for this video. Nothing was spent.', 'change_request');
|
|
309
434
|
}
|
|
310
435
|
const pin = { id: plan.style_id, version: plan.style_version, hash: plan.style_hash };
|
|
436
|
+
this.at = 'style';
|
|
437
|
+
const note = (why, fields) => deps.log.write('error', why, fields);
|
|
311
438
|
this.style = dev.styles
|
|
312
|
-
? await (0, style_1.readLocalStyle)(dev.styles, pin)
|
|
313
|
-
: await (0, style_1.fetchStyle)({ ref: handed.style_package, projectId: this.videoId, dir: this.layout.style, download: (u, m) => deps.line.download(u, m, this.stop.signal) });
|
|
439
|
+
? await (0, style_1.readLocalStyle)(dev.styles, pin, note)
|
|
440
|
+
: await (0, style_1.fetchStyle)({ ref: handed.style_package, projectId: this.videoId, dir: this.layout.style, download: (u, m) => deps.line.download(u, m, this.stop.signal), note });
|
|
314
441
|
await this.loadParts(!!dev.parts);
|
|
315
442
|
// The plan's files, checked against the hashes frozen at the yes.
|
|
443
|
+
this.at = 'plan-files';
|
|
316
444
|
const planFiles = {
|
|
317
445
|
dir: this.layout.inputs,
|
|
318
446
|
download: (u, m) => deps.line.download(u, m, this.stop.signal),
|
|
@@ -361,51 +489,70 @@ class Maker {
|
|
|
361
489
|
...this.layersOn().map((slot) => ({ key: `layer-${slot}`, ref: layerRefs?.[slot], slot })),
|
|
362
490
|
];
|
|
363
491
|
for (const { key, ref, slot } of wanted) {
|
|
492
|
+
this.at = key;
|
|
364
493
|
if (!ref || typeof ref.id !== 'string' || typeof ref.version !== 'string')
|
|
365
494
|
throw new errors_1.KitStop('This video names a part it has no version for. Nothing was spent.', 'change_request');
|
|
366
495
|
if (lock) {
|
|
367
496
|
const entry = lock.parts[ref.id];
|
|
368
|
-
if (!entry || entry.version !== ref.version)
|
|
369
|
-
|
|
497
|
+
if (!entry || entry.version !== ref.version) {
|
|
498
|
+
this.deps.log.write('error', 'part not in the parts list', { step: key, part: `${ref.id}@${ref.version}`, locked: entry?.version ?? null });
|
|
499
|
+
throw new errors_1.KitStop(`A part this video uses is not in this video’s parts list, so it can’t run. ${PLAN_CHANGE} Nothing was spent.`, 'change_request', 'not_locked');
|
|
500
|
+
}
|
|
370
501
|
const tooOld = (0, version_1.kitRangeRefusal)({ id: ref.id, version: ref.version, kit: entry.kit });
|
|
371
|
-
if (tooOld)
|
|
372
|
-
|
|
502
|
+
if (tooOld) {
|
|
503
|
+
this.deps.log.write('error', 'part needs another kit', { step: key, refusal: tooOld });
|
|
504
|
+
throw new errors_1.KitStop('This video needs a newer video kit. Nothing was spent.', 'update_kit', 'kit_range');
|
|
505
|
+
}
|
|
373
506
|
}
|
|
374
507
|
}
|
|
375
508
|
for (const { key, ref, slot } of wanted) {
|
|
509
|
+
this.at = key;
|
|
376
510
|
let loaded;
|
|
377
511
|
try {
|
|
378
512
|
loaded = await this.deps.host.loader.load({ ref, lock, dev, home: this.deps.home, env: this.deps.env, signal: this.stop.signal });
|
|
379
513
|
}
|
|
380
514
|
catch (error) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
515
|
+
this.deps.log.write('error', 'part could not be loaded', {
|
|
516
|
+
step: key,
|
|
517
|
+
part: `${ref.id}@${ref.version}`,
|
|
518
|
+
code: error instanceof loader_1.PartLoadError ? error.code : null,
|
|
519
|
+
error: error instanceof Error ? error.message : String(error),
|
|
520
|
+
});
|
|
521
|
+
throw loadStop(error);
|
|
387
522
|
}
|
|
388
523
|
const m = loaded.manifest;
|
|
389
|
-
|
|
390
|
-
|
|
524
|
+
const refused = (why, fields = {}) => this.deps.log.write('error', why, { step: key, part: `${ref.id}@${ref.version}`, ...fields });
|
|
525
|
+
if (m.id !== ref.id || m.version !== ref.version) {
|
|
526
|
+
refused('part loaded as another version', { loaded: `${m.id}@${m.version}` });
|
|
527
|
+
throw new errors_1.KitStop(`A part this video uses loaded as another version, so it can’t run. ${PLAN_CHANGE} Nothing was spent.`, 'change_request', 'manifest_mismatch');
|
|
528
|
+
}
|
|
391
529
|
const refusal = (0, version_1.interfaceRefusal)(m);
|
|
392
|
-
if (refusal)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
530
|
+
if (refusal) {
|
|
531
|
+
refused('part needs another interface', { refusal });
|
|
532
|
+
throw new errors_1.KitStop('This video needs a newer video kit. Nothing was spent.', 'update_kit', 'interface');
|
|
533
|
+
}
|
|
534
|
+
if (slot && (m.layer !== slot || m.kind !== SLOT_KIND[slot])) {
|
|
535
|
+
refused('part can’t fill its layer', { slot, layer: m.layer ?? null, kind: m.kind });
|
|
536
|
+
throw new errors_1.KitStop(`A part this video uses can’t do the job its plan gives it. ${PLAN_CHANGE} Nothing was spent.`, 'change_request', 'wrong_layer');
|
|
537
|
+
}
|
|
396
538
|
const lockModels = lock ? lock.parts[ref.id].models : m.needs.models;
|
|
397
539
|
const paid = m.needs.network || m.needs.models.length > 0 || m.kind.startsWith('generate_');
|
|
398
|
-
if (paid && (!Array.isArray(lockModels) || lockModels.length === 0))
|
|
399
|
-
|
|
540
|
+
if (paid && (!Array.isArray(lockModels) || lockModels.length === 0)) {
|
|
541
|
+
refused('paid part has no models in the parts list');
|
|
542
|
+
throw new errors_1.KitStop(`A part this video uses would order paid pieces its approved price doesn’t allow. ${PLAN_CHANGE} Nothing was spent.`, 'change_request', 'no_models');
|
|
543
|
+
}
|
|
400
544
|
const missing = this.missingNeeds(m.needs);
|
|
401
|
-
if (missing)
|
|
402
|
-
|
|
545
|
+
if (missing) {
|
|
546
|
+
refused('part needs what this computer lacks', { missing });
|
|
547
|
+
throw new errors_1.KitStop(`This computer can’t make this video: ${missing}. Run the computer check, then make the video again. Nothing was spent.`, 'refused', 'needs_missing');
|
|
548
|
+
}
|
|
403
549
|
try {
|
|
404
550
|
(0, schema_1.assertCheckable)(m.inputs);
|
|
405
551
|
(0, schema_1.assertCheckable)(m.outputs);
|
|
406
552
|
}
|
|
407
553
|
catch (error) {
|
|
408
|
-
|
|
554
|
+
refused('part schema can’t be checked', { error: error.message });
|
|
555
|
+
throw new errors_1.KitStop('This video needs a newer video kit. Nothing was spent.', 'update_kit', 'schema');
|
|
409
556
|
}
|
|
410
557
|
this.parts.set(key, loaded);
|
|
411
558
|
}
|
|
@@ -443,7 +590,7 @@ class Maker {
|
|
|
443
590
|
};
|
|
444
591
|
for (const step of this.style.style.timeline) {
|
|
445
592
|
const loaded = this.parts.get(step.id);
|
|
446
|
-
const { inputs, round } = fixed(step.id, (
|
|
593
|
+
const { inputs, round } = fixed(step.id, this.bind(step.id, step.inputs, scope));
|
|
447
594
|
const out = await this.runStep({ id: step.id, ref: step.part, loaded, inputs, models: this.modelsOf(step.part, loaded), fix: round }, hashes);
|
|
448
595
|
outputs.set(step.id, out);
|
|
449
596
|
if (out.timeline && typeof out.timeline === 'object')
|
|
@@ -469,8 +616,10 @@ class Maker {
|
|
|
469
616
|
continue;
|
|
470
617
|
}
|
|
471
618
|
const video = out.video;
|
|
472
|
-
if (!(0, canonical_1.isFileRef)(video) || video.media !== 'video')
|
|
473
|
-
|
|
619
|
+
if (!(0, canonical_1.isFileRef)(video) || video.media !== 'video') {
|
|
620
|
+
this.deps.log.write('error', 'layer made no video', { step: id });
|
|
621
|
+
throw new errors_1.KitStop(`Finishing this video gave back no video. ${PLAN_CHANGE}`, 'change_request', 'output_invalid');
|
|
622
|
+
}
|
|
474
623
|
cut = video;
|
|
475
624
|
if (out.timeline && typeof out.timeline === 'object')
|
|
476
625
|
timeline = out.timeline;
|
|
@@ -485,6 +634,19 @@ class Maker {
|
|
|
485
634
|
throw new errors_1.KitStop('The final check gave no verdict.', 'change_request');
|
|
486
635
|
return { cut, captions, verdict, steps: hashes };
|
|
487
636
|
}
|
|
637
|
+
/** A style step's inputs: a reference the kit can't resolve goes to the log, plain words to the person. */
|
|
638
|
+
bind(stepId, inputs, scope) {
|
|
639
|
+
this.at = stepId;
|
|
640
|
+
try {
|
|
641
|
+
return (0, bind_1.bindInputs)(inputs, scope, stepId);
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
if (!(error instanceof errors_1.KitStop))
|
|
645
|
+
throw error;
|
|
646
|
+
this.deps.log.write('error', 'step inputs could not be bound', { step: stepId, error: error.message });
|
|
647
|
+
throw new errors_1.KitStop(`One step of this video can’t get what it needs, so the video stopped there. ${PLAN_CHANGE}`, 'change_request', 'bad_input');
|
|
648
|
+
}
|
|
649
|
+
}
|
|
488
650
|
modelsOf(ref, loaded) {
|
|
489
651
|
return this.lock ? (this.lock.parts[ref.id]?.models ?? []) : loaded.manifest.needs.models;
|
|
490
652
|
}
|
|
@@ -502,10 +664,11 @@ class Maker {
|
|
|
502
664
|
/** One step: reused when its saved record has the same hash and intact files, else run (and retried once). */
|
|
503
665
|
async runStep(spec, hashes) {
|
|
504
666
|
const { manifest } = spec.loaded;
|
|
667
|
+
this.at = spec.id;
|
|
505
668
|
const inputErrors = (0, schema_1.schemaErrors)(manifest.inputs, spec.inputs, `${spec.id} inputs`);
|
|
506
669
|
if (inputErrors.length) {
|
|
507
670
|
this.deps.log.write('error', 'step inputs refused', { step: spec.id, errors: inputErrors });
|
|
508
|
-
throw new errors_1.KitStop(`
|
|
671
|
+
throw new errors_1.KitStop(`One step of this video was given something it can’t take, so the video stopped there. ${PLAN_CHANGE}`, 'change_request', 'bad_input');
|
|
509
672
|
}
|
|
510
673
|
const hash = (0, canonical_1.stepHash)({ interface: manifest.interface, part: spec.ref, toolchain: this.toolchain, inputs: spec.inputs, fix: spec.fix });
|
|
511
674
|
hashes[spec.id] = hash;
|
|
@@ -588,18 +751,20 @@ class Maker {
|
|
|
588
751
|
await this.saveRun();
|
|
589
752
|
if (stopped)
|
|
590
753
|
throw stopped;
|
|
591
|
-
this.deps.log.write('warn', 'step failed', { step: spec.id, attempt, code: failure.code });
|
|
754
|
+
this.deps.log.write('warn', 'step failed', { step: spec.id, attempt, code: failure.code, detail: record.error?.detail ?? null });
|
|
592
755
|
if (failure.retryable && attempt === 1 && manifest.retry?.transient !== 0)
|
|
593
756
|
continue;
|
|
757
|
+
const words = failureWords(failure);
|
|
758
|
+
const code = reportedCode(failure);
|
|
594
759
|
this.book.finish(spec.id, 'failed');
|
|
595
|
-
|
|
596
|
-
throw new errors_1.KitStop(
|
|
760
|
+
this.reportFailure(spec.id, code, words);
|
|
761
|
+
throw new errors_1.KitStop(words, failure.code === 'over_quote' ? 'stop' : 'failed', code);
|
|
597
762
|
}
|
|
598
763
|
finally {
|
|
599
764
|
link.done();
|
|
600
765
|
}
|
|
601
766
|
}
|
|
602
|
-
throw new errors_1.KitStop(failureWords(new errors_1.PartError('tool_failed')), 'failed');
|
|
767
|
+
throw new errors_1.KitStop(failureWords(new errors_1.PartError('tool_failed')), 'failed', 'tool_failed');
|
|
603
768
|
}
|
|
604
769
|
/** A part's output files must be in this video's folder (or the part's own) and unchanged. */
|
|
605
770
|
async checkOutputFiles(outputs, partDir, tmpDir) {
|
|
@@ -705,11 +870,27 @@ class Maker {
|
|
|
705
870
|
await this.writeFinal(null, verdict);
|
|
706
871
|
this.book.finish('layer-check', 'failed');
|
|
707
872
|
this.book.note = 'The final check found a problem';
|
|
708
|
-
|
|
873
|
+
// Check parts give each failed check words in `message`, beside the interface's fields; only plain ones reach the card.
|
|
874
|
+
const said = verdict.checks.find((c) => c.status === 'fail');
|
|
875
|
+
const message = typeof said?.message === 'string' ? said.message : '';
|
|
876
|
+
const detail = (0, plain_words_1.plainWords)(message, CHECK_FAILED, this.idNames());
|
|
877
|
+
this.deps.log.write('warn', 'the final check failed', { check: typeof said?.code === 'string' ? said.code : null, message, detail });
|
|
878
|
+
this.reportFailure('layer-check', 'check_failed', detail);
|
|
879
|
+
await this.reporter.flush();
|
|
709
880
|
this.run.status = 'failed';
|
|
710
881
|
await this.saveRun();
|
|
711
882
|
return { status: 'failed', message: 'The video didn’t pass the final check, so it wasn’t sent. Nothing more will be charged for it.' };
|
|
712
883
|
}
|
|
884
|
+
/** Part, step and layer ids, which the person never sees. */
|
|
885
|
+
idNames() {
|
|
886
|
+
const names = new Set();
|
|
887
|
+
for (const [key, loaded] of this.parts) {
|
|
888
|
+
names.add(key);
|
|
889
|
+
names.add(loaded.manifest.id);
|
|
890
|
+
names.add(`${loaded.manifest.id}@${loaded.manifest.version}`);
|
|
891
|
+
}
|
|
892
|
+
return [...names];
|
|
893
|
+
}
|
|
713
894
|
async writeFinal(result, verdict) {
|
|
714
895
|
await (0, promises_1.mkdir)(this.layout.final, { recursive: true, mode: 0o700 });
|
|
715
896
|
await (0, save_1.writeJson)(path.join(this.layout.final, 'check.json'), verdict);
|
|
@@ -737,6 +918,7 @@ class Maker {
|
|
|
737
918
|
/** Upload, our server's check, and one fix after a first failed check. */
|
|
738
919
|
async upload(result) {
|
|
739
920
|
let current = result;
|
|
921
|
+
this.at = 'upload';
|
|
740
922
|
for (;;) {
|
|
741
923
|
const final = (await this.writeFinal(current, current.verdict));
|
|
742
924
|
const data = await (0, promises_1.readFile)(final);
|
|
@@ -745,13 +927,13 @@ class Maker {
|
|
|
745
927
|
const sha256 = (0, canonical_1.sha256Hex)(data);
|
|
746
928
|
// Only the exact bytes the final check passed are sent.
|
|
747
929
|
if (sha256 !== current.cut.sha256 || data.length !== current.cut.bytes) {
|
|
748
|
-
throw new errors_1.KitStop('The finished video changed after its final check, so it wasn’t sent. Run the same command again to make it again.', 'failed');
|
|
930
|
+
throw new errors_1.KitStop('The finished video changed after its final check, so it wasn’t sent. Run the same command again to make it again.', 'failed', 'tool_failed');
|
|
749
931
|
}
|
|
750
932
|
let captions_vtt;
|
|
751
933
|
if (current.captions) {
|
|
752
934
|
const captions = await (0, promises_1.readFile)(current.captions.path);
|
|
753
935
|
if ((0, canonical_1.sha256Hex)(captions) !== current.captions.sha256 || captions.length !== current.captions.bytes) {
|
|
754
|
-
throw new errors_1.KitStop('The captions changed after the final check, so the video wasn’t sent. Run the same command again to make it again.', 'failed');
|
|
936
|
+
throw new errors_1.KitStop('The captions changed after the final check, so the video wasn’t sent. Run the same command again to make it again.', 'failed', 'tool_failed');
|
|
755
937
|
}
|
|
756
938
|
if (captions.length > types_1.MAX_CAPTIONS_BYTES)
|
|
757
939
|
throw new errors_1.KitStop('The captions file is larger than the line takes.', 'failed');
|