mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-28 18:59:47 +02:00
fix(skills): harden TasteForge multimodal contract
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: tasteforge-video
|
||||
description: Use when a user wants a taste interview for video work, to distill a visual aesthetic into structured, reusable constraints (a style pack), to validate or audit a style pack, to apply a pack's measured cadence and look to local footage, or to export an editable EDL/FCPXML cut. Also for auditing generated-media provenance and for deciding what is local-deterministic versus provider generation. All ECC-side operations are offline and deterministic; provider (Fal) generation fails closed here.
|
||||
description: Use for file-driven multimodal image, video, and 3D-asset discovery; taste interviews; distill or apply workflows; style-pack validation; editable EDL/FCPXML export; provenance audits; and offline planning that must fail closed before provider generation.
|
||||
metadata:
|
||||
origin: ECC
|
||||
---
|
||||
@@ -29,8 +29,12 @@ explains it and does not vendor or duplicate its code.
|
||||
- The user asks for a **generated-media provenance audit** — where did this
|
||||
pack, spec, or cut come from; what was measured locally versus generated by
|
||||
a provider; what was dry-run.
|
||||
- The user asks to **discover or plan file-driven multimodal image, video, or
|
||||
3D-asset outputs** from local reference files, including separate manifests,
|
||||
subject-anchored CV effects, or Resolve effect recipes.
|
||||
- The user mentions TasteForge, style packs, flashethereal, taste distillation,
|
||||
cadence/rhythm planning, or a taste interview for video.
|
||||
cadence/rhythm planning, multimodal discovery, distill/apply workflows, or a
|
||||
taste interview for video.
|
||||
|
||||
## Local Deterministic Operations vs Provider Generation
|
||||
|
||||
@@ -70,8 +74,8 @@ dry-run/dry_run semantics — say "dry-run spec" or "deterministic plan", never
|
||||
canonical local GitHub checkout root (never a hard-coded machine path);
|
||||
package directory `tasteforge/`.
|
||||
- CLI: `python3 -m tasteforge <command>` — `provenance`, `inspect`, `validate`,
|
||||
`interview`, `distill`, `apply`, `export`. `--live` flags exit with code 2
|
||||
and refuse.
|
||||
`interview`, `distill`, `apply`, `export`, `multimodal`. `--live` flags exit
|
||||
with code 2 and refuse.
|
||||
- Schemas are the contract: taste profile, pack manifest, grade, cadence,
|
||||
spec, timeline events, application reports (`provider` is enum-locked to
|
||||
`"none"`; `dry_run` to `true`).
|
||||
@@ -125,16 +129,23 @@ genres distinct rather than blending them into one generic aesthetic:
|
||||
The command measures local references with ffprobe/ffmpeg and emits one style
|
||||
spec per genre, separate image, video, and 3D-asset manifests, provenance, and
|
||||
a Resolve effect recipe. The effect schedule must be seeded aperiodic. CV
|
||||
effects require a real subject anchor with fail-closed track-loss behavior.
|
||||
Every effect carries placement constraints that preserve faces and readable
|
||||
type and prevent decorative corner meshes from replacing full-frame 3D work.
|
||||
effects require a real subject anchor whose exact lost-track policy is
|
||||
`disable_effect_until_track_recovers`; `continue_without_anchor` and every
|
||||
other policy fail closed. Every effect carries placement constraints that
|
||||
preserve faces and readable type and prevent decorative corner meshes from
|
||||
replacing full-frame 3D work.
|
||||
|
||||
The returned receipt is the bundle boundary. It binds every emitted evidence
|
||||
artifact by relative path, byte size, SHA-256, genre, modality,
|
||||
`provider_execution: false`, and exact reference/time provenance. Manifests and
|
||||
requests also require `provider_calls: 0`, `provider_execution: false`,
|
||||
`submit: false`, and disabled provider-call mode. Whole-file evidence uses an
|
||||
explicit whole-file time basis and never invents timestamps.
|
||||
`provider_execution:false`, and exact reference/time provenance. The receipt
|
||||
itself requires `provider_calls:0`, `provider_execution:false`, and
|
||||
`dry_run:true`. Every modality manifest and every nested request must contain
|
||||
all four exact fail-closed fields: `provider_calls:0`,
|
||||
`provider_execution:false`, `dry_run:true`, and `submit:false`; each request
|
||||
also requires `provider_call_mode:"disabled"`. A missing field is a rejection,
|
||||
not a default, and `dry_run:false` must be rejected before output is written.
|
||||
Whole-file evidence uses an explicit whole-file time basis and never invents
|
||||
timestamps.
|
||||
|
||||
Always run bundle validation after creation. A missing image, video, or 3D-asset
|
||||
manifest must fail closed. Genericized or duplicate genres, periodic schedules,
|
||||
|
||||
@@ -20,6 +20,37 @@ function readJson(relativePath) {
|
||||
return JSON.parse(read(relativePath));
|
||||
}
|
||||
|
||||
function assertExactDryRunBoundary(payload, label) {
|
||||
assert.strictEqual(payload.provider_calls, 0, `${label} must require provider_calls:0`);
|
||||
assert.strictEqual(payload.provider_execution, false, `${label} must require provider_execution:false`);
|
||||
assert.strictEqual(payload.dry_run, true, `${label} must require dry_run:true`);
|
||||
assert.strictEqual(payload.submit, false, `${label} must require submit:false`);
|
||||
}
|
||||
|
||||
function validateRejectedContractFixture(fixture) {
|
||||
if (fixture.kind === "manifest") {
|
||||
assertExactDryRunBoundary(fixture.payload, "manifest");
|
||||
for (const request of fixture.payload.requests || []) {
|
||||
assertExactDryRunBoundary(request, "request");
|
||||
assert.strictEqual(request.provider_call_mode, "disabled");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (fixture.kind === "effect_recipe") {
|
||||
for (const event of fixture.payload.events || []) {
|
||||
if (event.requires_subject_anchor) {
|
||||
assert.strictEqual(
|
||||
event.subject_anchor?.lost_policy,
|
||||
"disable_effect_until_track_recovers",
|
||||
"anchored CV effects must disable_effect_until_track_recovers"
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
assert.fail(`unknown fixture kind: ${fixture.kind}`);
|
||||
}
|
||||
|
||||
const tests = [];
|
||||
function test(name, fn) { tests.push([name, fn]); }
|
||||
|
||||
@@ -37,6 +68,15 @@ test("has valid discoverable frontmatter and trigger phrases", () => {
|
||||
/export EDL\/FCPXML|export .*EDL.*FCPXML/i,
|
||||
/audit .*generated-media provenance|provenance audit/i,
|
||||
]) assert.match(skill, trigger);
|
||||
|
||||
const description = skill.match(/^description: ([^\n]+)$/m)?.[1] || "";
|
||||
for (const discoveryTerm of ["multimodal", "image", "video", "3D", "file-driven", "distill", "apply"]) {
|
||||
assert.match(description, new RegExp(discoveryTerm, "i"), `frontmatter misses ${discoveryTerm}`);
|
||||
}
|
||||
const triggers = skill.match(/## When to Use\n([\s\S]*?)\n## /)?.[1] || "";
|
||||
for (const discoveryTerm of ["multimodal", "image", "video", "3D", "file-driven", "distill", "apply"]) {
|
||||
assert.match(triggers, new RegExp(discoveryTerm, "i"), `triggers miss ${discoveryTerm}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("distinguishes local deterministic operations from provider generation", () => {
|
||||
@@ -97,11 +137,29 @@ test("defines the fail-closed file-driven multimodal contract", () => {
|
||||
/genre.*modality/is,
|
||||
/exact reference\/time provenance/i,
|
||||
/provider_calls:\s*0/i,
|
||||
/dry_run:\s*true/i,
|
||||
/submit:\s*false/i,
|
||||
/disable_effect_until_track_recovers/i,
|
||||
]) assert.match(skill, phrase);
|
||||
assert.match(skill, /missing.*manifest.*fail closed/is);
|
||||
assert.match(skill, /tamper.*fail closed/is);
|
||||
});
|
||||
|
||||
test("executable fixtures reject dry_run:false and continue_without_anchor", () => {
|
||||
for (const fixtureName of [
|
||||
"reject-dry-run-false.json",
|
||||
"reject-continue-without-anchor.json",
|
||||
]) {
|
||||
const fixture = readJson(`tests/fixtures/tasteforge-video/${fixtureName}`);
|
||||
assert.strictEqual(fixture.expected, "reject");
|
||||
assert.throws(
|
||||
() => validateRejectedContractFixture(fixture),
|
||||
undefined,
|
||||
`${fixtureName} was not rejected`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("ships through the opt-in media-generation install module and npm package", () => {
|
||||
const modules = readJson("manifests/install-modules.json").modules;
|
||||
const module = modules.find((candidate) => candidate.id === "media-generation");
|
||||
@@ -166,8 +224,10 @@ test("passes the curated skill validator", () => {
|
||||
// ECC_TEST_NPM_PACK=1 (release/CI verification); the default suite relies on
|
||||
// the files-array assertions above.
|
||||
test("ships inside the real npm tarball (opt-in)", () => {
|
||||
if (process.env.ECC_TEST_NPM_PACK !== "1") return;
|
||||
const result = spawnSync("npm", ["pack", "--dry-run"], {
|
||||
if (process.env.ECC_TEST_NPM_PACK !== "1") {
|
||||
return { skipped: "set ECC_TEST_NPM_PACK=1 to run real npm pack inclusion" };
|
||||
}
|
||||
const result = spawnSync("npm", ["pack", "--dry-run", "--ignore-scripts"], {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: "utf8",
|
||||
});
|
||||
@@ -180,10 +240,16 @@ test("ships inside the real npm tarball (opt-in)", () => {
|
||||
});
|
||||
|
||||
let failed = 0;
|
||||
let skipped = 0;
|
||||
console.log("\n=== Testing TasteForge video skill ===\n");
|
||||
for (const [name, fn] of tests) {
|
||||
try {
|
||||
fn();
|
||||
const result = fn();
|
||||
if (result?.skipped) {
|
||||
skipped += 1;
|
||||
console.log(` - SKIP ${name}: ${result.skipped}`);
|
||||
continue;
|
||||
}
|
||||
console.log(` ✓ ${name}`);
|
||||
} catch (error) {
|
||||
failed += 1;
|
||||
@@ -192,4 +258,4 @@ for (const [name, fn] of tests) {
|
||||
}
|
||||
}
|
||||
if (failed) process.exit(1);
|
||||
console.log(`\n${tests.length - failed}/${tests.length} passed`);
|
||||
console.log(`\n${tests.length - failed - skipped}/${tests.length} passed, ${skipped} skipped`);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"kind": "effect_recipe",
|
||||
"expected": "reject",
|
||||
"payload": {
|
||||
"events": [
|
||||
{
|
||||
"effect": "cv_subject_glitch",
|
||||
"requires_subject_anchor": true,
|
||||
"subject_anchor": {
|
||||
"mode": "object_track",
|
||||
"target": "primary_subject",
|
||||
"source_ref_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"evidence_time": 0,
|
||||
"lost_policy": "continue_without_anchor"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"kind": "manifest",
|
||||
"expected": "reject",
|
||||
"payload": {
|
||||
"modality": "video",
|
||||
"provider_calls": 0,
|
||||
"provider_execution": false,
|
||||
"dry_run": true,
|
||||
"submit": false,
|
||||
"requests": [
|
||||
{
|
||||
"provider_calls": 0,
|
||||
"provider_execution": false,
|
||||
"dry_run": false,
|
||||
"submit": false,
|
||||
"provider_call_mode": "disabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user