@mcptoolshop/style-dataset-lab 3.4.0 → 3.5.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/CHANGELOG.md +27 -0
- package/README.es.md +111 -59
- package/README.fr.md +117 -67
- package/README.hi.md +109 -70
- package/README.it.md +111 -60
- package/README.ja.md +116 -76
- package/README.md +58 -2
- package/README.pt-BR.md +119 -64
- package/README.zh.md +108 -67
- package/bin/sdlab.js +50 -1
- package/lib/adapters/ai-toolkit.js +187 -30
- package/lib/adapters/comfyui-workflows.js +84 -9
- package/lib/asset-ingest.js +575 -0
- package/lib/asset-masks.js +323 -0
- package/lib/asset-source.js +1176 -0
- package/lib/canon-author.js +350 -0
- package/lib/canon-review-gate.js +205 -0
- package/lib/colab-launcher-guards.js +285 -0
- package/lib/export.js +1 -1
- package/lib/freeze-gate.js +3 -0
- package/lib/generated-provenance.js +178 -5
- package/lib/lane1-render.js +668 -0
- package/lib/memory-store.js +663 -0
- package/lib/model-licences.js +305 -0
- package/lib/npy-meta.js +130 -0
- package/lib/png-meta.js +512 -0
- package/lib/records.js +54 -8
- package/lib/reingest-selected.js +15 -1
- package/lib/reingest.js +27 -3
- package/lib/run-manifest.js +59 -11
- package/lib/train-runs.js +214 -0
- package/lib/verifier.js +3 -0
- package/lib/verify-chain.js +367 -0
- package/package.json +2 -2
- package/scripts/asset-ingest.js +160 -0
- package/scripts/canon-draft.js +82 -0
- package/scripts/canon-freeze.js +4 -0
- package/scripts/colab/REAL_SERVICE_ASSUMPTIONS.md +14 -0
- package/scripts/colab/ai-toolkit-override-py315.txt +45 -0
- package/scripts/colab/colab_window.sh +587 -0
- package/scripts/colab/lane1_render.py +970 -0
- package/scripts/colab/launch_guards.py +564 -0
- package/scripts/colab/launch_lane1_stub.sh +309 -0
- package/scripts/colab/launch_lane3_stub.sh +366 -0
- package/scripts/colab/remote_runner.py +59 -0
- package/scripts/doctor.js +1 -0
- package/scripts/generate.js +38 -12
- package/scripts/verify.js +67 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Remote kernel runner executed via `colab exec -s <session> -f remote_runner.py`.
|
|
3
|
+
|
|
4
|
+
Runs the uploaded remote shell script (`SDLAB_REMOTE_SCRIPT`, e.g.
|
|
5
|
+
`/content/remote_lane1_stub.sh` or `/content/remote_lane3_stub.sh`) via
|
|
6
|
+
`subprocess.Popen`, streaming each stdout/stderr line to the IPython kernel's
|
|
7
|
+
stdout as it arrives so the rig's vitals and DEAD HARNESS watcher read it in
|
|
8
|
+
real time, and finishes by printing a `SDLAB_REMOTE_RC=<rc>` marker line.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _resolve_bash(env: dict[str, str]) -> str:
|
|
20
|
+
git_bash = "C:/Program Files/Git/bin/bash.exe"
|
|
21
|
+
if os.name == "nt" and os.path.exists(git_bash):
|
|
22
|
+
return git_bash
|
|
23
|
+
found = shutil.which("bash", path=env.get("PATH"))
|
|
24
|
+
return found or "bash"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def run_remote_script(
|
|
28
|
+
script_path: str | None = None,
|
|
29
|
+
*,
|
|
30
|
+
env: dict[str, str] | None = None,
|
|
31
|
+
) -> int:
|
|
32
|
+
effective_env = dict(os.environ if env is None else env)
|
|
33
|
+
target_script = script_path or effective_env.get("SDLAB_REMOTE_SCRIPT", "").strip()
|
|
34
|
+
if not target_script:
|
|
35
|
+
print("REFUSED: SDLAB_REMOTE_SCRIPT is required", file=sys.stderr, flush=True)
|
|
36
|
+
print("SDLAB_REMOTE_RC=2", flush=True)
|
|
37
|
+
return 2
|
|
38
|
+
|
|
39
|
+
cmd = [_resolve_bash(effective_env), target_script]
|
|
40
|
+
proc = subprocess.Popen(
|
|
41
|
+
cmd,
|
|
42
|
+
stdin=subprocess.DEVNULL,
|
|
43
|
+
stdout=subprocess.PIPE,
|
|
44
|
+
stderr=subprocess.STDOUT,
|
|
45
|
+
text=True,
|
|
46
|
+
bufsize=1,
|
|
47
|
+
env=effective_env,
|
|
48
|
+
)
|
|
49
|
+
assert proc.stdout is not None
|
|
50
|
+
for line in proc.stdout:
|
|
51
|
+
sys.stdout.write(line)
|
|
52
|
+
sys.stdout.flush()
|
|
53
|
+
rc = int(proc.wait())
|
|
54
|
+
print(f"SDLAB_REMOTE_RC={rc}", flush=True)
|
|
55
|
+
return rc
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
run_remote_script()
|
package/scripts/doctor.js
CHANGED
|
@@ -151,6 +151,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
151
151
|
|
|
152
152
|
if (meta.domain) { pass(`project.json: domain = "${meta.domain}"`); passes++; }
|
|
153
153
|
else { warn('project.json: no "domain" set'); warnings++; }
|
|
154
|
+
if (meta.display_name === 'TEMPLATE') { fail('project.json: display_name is still "TEMPLATE"', 'Set a real display_name in project.json instead of the unedited template placeholder.'); failures++; }
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
// ── 5. Constitution validation ──
|
package/scripts/generate.js
CHANGED
|
@@ -19,8 +19,10 @@ import { readJsonFile, loadProjectMeta, resolveGpuModel } from "../lib/config.js
|
|
|
19
19
|
import { runtimeError, inputError, handleCliError } from "../lib/errors.js";
|
|
20
20
|
import { comfyHealth, submitAndWait, downloadImage } from "../lib/comfyui.js";
|
|
21
21
|
import { pickOutputImage } from "../lib/comfyui-output.js";
|
|
22
|
-
import { buildQwenGraph, QWEN_DEFAULTS } from "../lib/adapters/comfyui-workflows.js";
|
|
22
|
+
import { buildQwenGraph, QWEN_DEFAULTS, resolveQwenLoras } from "../lib/adapters/comfyui-workflows.js";
|
|
23
23
|
import { buildPinning, SEED_POLICIES, createHashCache, flushHashCache } from "../lib/run-manifest.js";
|
|
24
|
+
import { STUDIO_OWNER, STUDIO_LICENCE, buildDefaultRights } from "../lib/records.js";
|
|
25
|
+
import { resolveCanonLink } from "../lib/generated-provenance.js";
|
|
24
26
|
import { info, result, warn } from "../lib/log.js";
|
|
25
27
|
|
|
26
28
|
/**
|
|
@@ -226,7 +228,7 @@ function findPlaceholderPrompts(jobs) {
|
|
|
226
228
|
* `base` resolves: input.base → input.defaults.base → project.json defaults.base
|
|
227
229
|
* → "sdxl". `"qwen-image"` routes to the non-anime Qwen graph.
|
|
228
230
|
*/
|
|
229
|
-
function normalizeGenerationInput(raw, projectMeta) {
|
|
231
|
+
export function normalizeGenerationInput(raw, projectMeta) {
|
|
230
232
|
if (!Array.isArray(raw.subjects) || raw.subjects.length === 0) {
|
|
231
233
|
throw inputError(
|
|
232
234
|
'INPUT_MISSING_FIELDS',
|
|
@@ -260,10 +262,11 @@ function normalizeGenerationInput(raw, projectMeta) {
|
|
|
260
262
|
jobs.push({
|
|
261
263
|
assetId: `${subject.id}_${variation.id}`,
|
|
262
264
|
subjectId: subject.id,
|
|
265
|
+
entryId: subject.entry_id || subject.id,
|
|
263
266
|
variationId: variation.id,
|
|
264
267
|
prompt: compose(subject.prompt),
|
|
265
268
|
seed: baseSeed + k + (variation.seed_offset || 0),
|
|
266
|
-
loras: variation.loras || [],
|
|
269
|
+
loras: variation.loras || subject.loras || [],
|
|
267
270
|
});
|
|
268
271
|
k++;
|
|
269
272
|
}
|
|
@@ -275,14 +278,16 @@ function normalizeGenerationInput(raw, projectMeta) {
|
|
|
275
278
|
const negative = raw.negative ?? defaults.negative ?? '';
|
|
276
279
|
for (const subject of raw.subjects) {
|
|
277
280
|
const nvar = Number.isFinite(subject.variations) ? subject.variations : 1;
|
|
281
|
+
const subjectLoras = subject.loras || defaults.loras || raw.loras || [];
|
|
278
282
|
for (let v = 0; v < nvar; v++) {
|
|
279
283
|
jobs.push({
|
|
280
284
|
assetId: `${subject.id}_v${v}`,
|
|
281
285
|
subjectId: subject.id,
|
|
286
|
+
entryId: subject.entry_id || subject.id,
|
|
282
287
|
variationId: `v${v}`,
|
|
283
288
|
prompt: compose(subject.prompt),
|
|
284
289
|
seed: baseSeed + k,
|
|
285
|
-
loras:
|
|
290
|
+
loras: subjectLoras,
|
|
286
291
|
});
|
|
287
292
|
k++;
|
|
288
293
|
}
|
|
@@ -384,7 +389,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
384
389
|
const runStartMs = Date.now();
|
|
385
390
|
|
|
386
391
|
for (const job of pack.jobs) {
|
|
387
|
-
const { assetId, prompt: fullPrompt, seed, loras } = job;
|
|
392
|
+
const { assetId, entryId, prompt: fullPrompt, seed, loras } = job;
|
|
393
|
+
const effectiveLoras = isQwen
|
|
394
|
+
? resolveQwenLoras({ projectDefaults: d, loras })
|
|
395
|
+
: loras;
|
|
388
396
|
|
|
389
397
|
// --resume: skip jobs whose record + image are both already on disk.
|
|
390
398
|
// Seeds are precomputed per job index, so skipping never shifts later seeds.
|
|
@@ -399,7 +407,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
399
407
|
}
|
|
400
408
|
}
|
|
401
409
|
|
|
402
|
-
console.log(` [${generated + 1}/${totalExpected}] ${assetId} (seed: ${seed}, loras: ${
|
|
410
|
+
console.log(` [${generated + 1}/${totalExpected}] ${assetId} (seed: ${seed}, loras: ${effectiveLoras.length})`);
|
|
403
411
|
|
|
404
412
|
if (dryRun) {
|
|
405
413
|
generated++;
|
|
@@ -424,12 +432,12 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
424
432
|
vae: d.vae || QWEN_DEFAULTS.vae,
|
|
425
433
|
clipType: d.clip_type || QWEN_DEFAULTS.clip_type,
|
|
426
434
|
weightDtype: d.weight_dtype || QWEN_DEFAULTS.weight_dtype,
|
|
427
|
-
loras:
|
|
435
|
+
loras: effectiveLoras,
|
|
428
436
|
filenamePrefix: 'sdl',
|
|
429
437
|
})
|
|
430
438
|
: buildWorkflow(
|
|
431
439
|
fullPrompt, pack.negative,
|
|
432
|
-
d.checkpoint,
|
|
440
|
+
d.checkpoint, effectiveLoras, seed,
|
|
433
441
|
resolved.steps, resolved.cfg, resolved.sampler, resolved.scheduler,
|
|
434
442
|
resolved.width, resolved.height,
|
|
435
443
|
);
|
|
@@ -452,9 +460,16 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
452
460
|
const destPath = `outputs/candidates/${assetId}.png`;
|
|
453
461
|
await writeFile(join(GAME_ROOT, destPath), imgData);
|
|
454
462
|
|
|
463
|
+
const canonLink = resolveCanonLink(GAME_ROOT, {
|
|
464
|
+
canonSha: raw.canon_sha || null,
|
|
465
|
+
entryId,
|
|
466
|
+
});
|
|
467
|
+
|
|
455
468
|
// Write provenance record (base-aware: Qwen records unet/clip/vae/shift,
|
|
456
469
|
// SDXL records checkpoint/loras).
|
|
457
470
|
const provenance = {
|
|
471
|
+
owner: STUDIO_OWNER,
|
|
472
|
+
licence: STUDIO_LICENCE,
|
|
458
473
|
workflow_id: pack.lane,
|
|
459
474
|
workflow_version: "1.0.0",
|
|
460
475
|
base,
|
|
@@ -470,16 +485,23 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
470
485
|
generation_time_ms: elapsed,
|
|
471
486
|
gpu_model: gpuModel,
|
|
472
487
|
batch_index: generated,
|
|
488
|
+
...(canonLink
|
|
489
|
+
? {
|
|
490
|
+
canon_sha: canonLink.canon_sha,
|
|
491
|
+
entry_id: canonLink.entry_id,
|
|
492
|
+
entry_sha256: canonLink.entry_sha256,
|
|
493
|
+
}
|
|
494
|
+
: {}),
|
|
473
495
|
};
|
|
474
496
|
if (isQwen) {
|
|
475
497
|
provenance.unet = d.unet || QWEN_DEFAULTS.unet;
|
|
476
498
|
provenance.clip = d.clip || QWEN_DEFAULTS.clip;
|
|
477
499
|
provenance.vae = d.vae || QWEN_DEFAULTS.vae;
|
|
478
500
|
provenance.shift = d.shift ?? QWEN_DEFAULTS.shift;
|
|
479
|
-
provenance.loras =
|
|
501
|
+
provenance.loras = effectiveLoras;
|
|
480
502
|
} else {
|
|
481
503
|
provenance.checkpoint = d.checkpoint;
|
|
482
|
-
provenance.loras =
|
|
504
|
+
provenance.loras = effectiveLoras;
|
|
483
505
|
}
|
|
484
506
|
|
|
485
507
|
// PIN_PER_STEP: pin the exact graph + model/LoRA content identity + seed
|
|
@@ -490,21 +512,25 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
490
512
|
models: isQwen
|
|
491
513
|
? { unet: provenance.unet, clip: provenance.clip, vae: provenance.vae }
|
|
492
514
|
: { checkpoint: d.checkpoint },
|
|
493
|
-
loras:
|
|
515
|
+
loras: effectiveLoras,
|
|
494
516
|
seedPolicy: SEED_POLICIES.BASE_INCREMENT,
|
|
495
517
|
hashModels,
|
|
496
518
|
cache: hashCache,
|
|
497
519
|
});
|
|
520
|
+
provenance.comfy_workflow_sha = provenance.pinning.comfy_workflow_sha;
|
|
498
521
|
|
|
499
522
|
const record = {
|
|
500
523
|
id: assetId,
|
|
501
524
|
schema_version: "1.0.0",
|
|
502
525
|
created_at: new Date().toISOString(),
|
|
526
|
+
owner: STUDIO_OWNER,
|
|
527
|
+
licence: STUDIO_LICENCE,
|
|
528
|
+
rights: buildDefaultRights(),
|
|
503
529
|
asset_path: destPath,
|
|
504
530
|
image: { format: "png", width: resolved.width, height: resolved.height, bytes: imgData.length },
|
|
505
531
|
provenance,
|
|
506
532
|
judgment: null,
|
|
507
|
-
canon:
|
|
533
|
+
canon: canonLink,
|
|
508
534
|
iteration: null,
|
|
509
535
|
};
|
|
510
536
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* sdlab verify — Offline end-to-end provenance chain verification (G9, §2.2–§2.3).
|
|
5
|
+
*
|
|
6
|
+
* Walks:
|
|
7
|
+
* image -> record (G5 owner + CC-BY-4.0)
|
|
8
|
+
* -> canon (G6 canon_sha + entry_sha256)
|
|
9
|
+
* -> model licence (G4 SPDX id + licence_url + date_read)
|
|
10
|
+
* -> run manifest (workflow sha + quarantine guard + verdict separation)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { parseArgs, getProjectName } from '../lib/args.js';
|
|
14
|
+
import { getProjectRoot } from '../lib/paths.js';
|
|
15
|
+
import { inputError, handleCliError } from '../lib/errors.js';
|
|
16
|
+
import { verifyProvenanceChain } from '../lib/verify-chain.js';
|
|
17
|
+
|
|
18
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
19
|
+
const parsed = parseArgs(argv, {
|
|
20
|
+
flags: {
|
|
21
|
+
project: { type: 'string' },
|
|
22
|
+
game: { type: 'string' },
|
|
23
|
+
record: { type: 'string' },
|
|
24
|
+
snapshot: { type: 'string' },
|
|
25
|
+
json: { type: 'boolean' },
|
|
26
|
+
'allow-empty': { type: 'boolean' },
|
|
27
|
+
},
|
|
28
|
+
deprecated: { game: 'project' },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const projectName = getProjectName(argv);
|
|
32
|
+
const projectDir = getProjectRoot(projectName);
|
|
33
|
+
|
|
34
|
+
const report = await verifyProvenanceChain(projectDir, {
|
|
35
|
+
recordId: parsed.flags.record || null,
|
|
36
|
+
snapshotId: parsed.flags.snapshot || null,
|
|
37
|
+
allowEmpty: parsed.flags['allow-empty'] === true,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (parsed.flags.json) {
|
|
41
|
+
console.log(JSON.stringify(report, null, 2));
|
|
42
|
+
} else {
|
|
43
|
+
console.log(`\x1b[1msdlab verify\x1b[0m — ${projectName}`);
|
|
44
|
+
console.log(` Records checked: ${report.records_checked} (${report.passed_count} passed, ${report.failed_count} failed)`);
|
|
45
|
+
for (const f of report.failures) {
|
|
46
|
+
console.error(` \x1b[31m✗\x1b[0m [${f.code}] ${f.message}`);
|
|
47
|
+
}
|
|
48
|
+
if (report.ok) {
|
|
49
|
+
console.log(` \x1b[32m✓\x1b[0m Provenance chain intact (CC-BY-4.0 eligible)`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!report.ok) {
|
|
54
|
+
const codes = [...new Set(report.failures.map((f) => f.code))].join(', ');
|
|
55
|
+
throw inputError(
|
|
56
|
+
'PROVENANCE_VERIFY_FAILED',
|
|
57
|
+
`Provenance verification failed for project "${projectName}" (${report.failures.length} failure(s): ${codes})`,
|
|
58
|
+
'Fix the broken provenance link(s) reported above before releasing or exporting under CC-BY-4.0.',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return report;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (process.argv[1] && (process.argv[1].endsWith('verify.js') || process.argv[1].endsWith('verify'))) {
|
|
66
|
+
run().catch(handleCliError);
|
|
67
|
+
}
|