Merge pull request #2824 from affaan-m/agent/tasteforge-multimodal-20260819
feat(skills): add TasteForge multimodal workflow
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"source": "./",
|
||||
"description": "Harness-native ECC operator layer - 68 agents, 285 skills, 94 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
|
||||
"description": "Harness-native ECC operator layer - 68 agents, 286 skills, 94 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
|
||||
"version": "2.2.0",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"version": "2.2.0",
|
||||
"description": "Harness-native ECC plugin for engineering teams - 68 agents, 285 skills, 94 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
|
||||
"description": "Harness-native ECC plugin for engineering teams - 68 agents, 286 skills, 94 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
"url": "https://x.com/affaanmustafa"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Everything Claude Code (ECC) — Agent Instructions
|
||||
|
||||
This is a **production-ready AI coding plugin** providing 68 specialized agents, 285 skills, 94 commands, and automated hook workflows for software development.
|
||||
This is a **production-ready AI coding plugin** providing 68 specialized agents, 286 skills, 94 commands, and automated hook workflows for software development.
|
||||
|
||||
**Version:** 2.2.0
|
||||
|
||||
@@ -154,7 +154,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
|
||||
|
||||
```
|
||||
agents/ — 68 specialized subagents
|
||||
skills/ — 285 workflow skills and domain knowledge
|
||||
skills/ — 286 workflow skills and domain knowledge
|
||||
commands/ — 94 slash commands
|
||||
hooks/ — Trigger-based automations
|
||||
rules/ — Always-follow guidelines (common + per-language)
|
||||
|
||||
@@ -145,12 +145,12 @@ Instead of rebuilding that process in every prompt, you install it once and make
|
||||
|
||||
ECC is MIT-licensed open source. It works best with Claude Code today, has a supported Codex sync path, and provides capability-limited adapters for Cursor, OpenCode, Gemini, Zed, GitHub Copilot, Antigravity, Qwen, and other harnesses. See the [support status matrix](#platform-support) before assuming feature parity.
|
||||
|
||||
Access to 68 agents, 285 skills, and 94 legacy command shims, plus hooks, rules, memory, continuous learning, and AgentShield security scanning. The agents are specialized for planning, review, build repair, security, architecture, and domain work.
|
||||
Access to 68 agents, 286 skills, and 94 legacy command shims, plus hooks, rules, memory, continuous learning, and AgentShield security scanning. The agents are specialized for planning, review, build repair, security, architecture, and domain work.
|
||||
|
||||
| Included | Count | What it gives you |
|
||||
| ---------------- | ----------: | ------------------------------------------------------------------------------------ |
|
||||
| Agents | 68 agents | Planning, review, build repair, security, architecture, and domain work |
|
||||
| Skills | 285 skills | TDD, research, security, docs, frontend, data, ML, operations, and more |
|
||||
| Skills | 286 skills | TDD, research, security, docs, frontend, data, ML, operations, and more |
|
||||
| Commands | 94 commands | Convenient entry points while ECC moves to a skills-first surface |
|
||||
| Hooks and memory | Runtime | Enforcement, session summaries, continuous learning, instincts, and context controls |
|
||||
| Rules | Selective | Always-loaded standards you choose by language or project |
|
||||
|
||||
+1
-1
@@ -196,7 +196,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**完成!** 你现在可以使用 68 个代理、285 个技能和 94 个命令。
|
||||
**完成!** 你现在可以使用 68 个代理、286 个技能和 94 个命令。
|
||||
|
||||
### multi-* 命令需要额外配置
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# Everything Claude Code (ECC) — Agent Talimatları
|
||||
|
||||
Bu, yazılım geliştirme için 68 özel agent, 285 skill, 94 command ve otomatik hook iş akışları sağlayan **üretime hazır bir AI kodlama eklentisidir**.
|
||||
Bu, yazılım geliştirme için 68 özel agent, 286 skill, 94 command ve otomatik hook iş akışları sağlayan **üretime hazır bir AI kodlama eklentisidir**.
|
||||
|
||||
**Sürüm:** 2.2.0
|
||||
|
||||
@@ -142,7 +142,7 @@ Başarısızlık sorunlarını giderin: test izolasyonunu kontrol edin → mockl
|
||||
|
||||
```
|
||||
agents/ — 68 özel subagent
|
||||
skills/ — 285 iş akışı skillleri ve alan bilgisi
|
||||
skills/ — 286 iş akışı skillleri ve alan bilgisi
|
||||
commands/ — 94 slash command
|
||||
hooks/ — Tetikleyici tabanlı otomasyonlar
|
||||
rules/ — Her zaman uyulması gereken kurallar (ortak + dile özel)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Everything Claude Code (ECC) — 智能体指令
|
||||
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 68 个专业代理、285 项技能、94 条命令以及自动化钩子工作流,用于软件开发。
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 68 个专业代理、286 项技能、94 条命令以及自动化钩子工作流,用于软件开发。
|
||||
|
||||
**版本:** 2.2.0
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
```
|
||||
agents/ — 68 个专业子代理
|
||||
skills/ — 285 个工作流技能和领域知识
|
||||
skills/ — 286 个工作流技能和领域知识
|
||||
commands/ — 94 个斜杠命令
|
||||
hooks/ — 基于触发的自动化
|
||||
rules/ — 始终遵循的指导方针(通用 + 每种语言)
|
||||
|
||||
@@ -260,7 +260,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**搞定!** 你现在可以使用 68 个智能体、285 项技能和 94 个命令了。
|
||||
**搞定!** 你现在可以使用 68 个智能体、286 项技能和 94 个命令了。
|
||||
|
||||
***
|
||||
|
||||
@@ -1174,7 +1174,7 @@ opencode
|
||||
|---------|---------------|----------|--------|
|
||||
| 智能体 | PASS: 68 个 | PASS: 12 个 | **Claude Code 领先** |
|
||||
| 命令 | PASS: 94 个 | PASS: 35 个 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 285 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 286 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
|
||||
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
|
||||
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
|
||||
@@ -1282,7 +1282,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|
||||
|---------|-----------------------|------------|-----------|----------|
|
||||
| **智能体** | 68 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
|
||||
| **命令** | 94 | 共享 | 基于指令 | 35 |
|
||||
| **技能** | 285 | 共享 | 10 (原生格式) | 37 |
|
||||
| **技能** | 286 | 共享 | 10 (原生格式) | 37 |
|
||||
| **钩子事件** | 8 种类型 | 15 种类型 | SessionStart(1 种类型) | 11 种类型 |
|
||||
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | 1 个 SessionStart 引导脚本 | 插件钩子 |
|
||||
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |
|
||||
|
||||
@@ -705,7 +705,8 @@
|
||||
"skills/ui-demo",
|
||||
"skills/video-editing",
|
||||
"skills/videodb",
|
||||
"skills/taste"
|
||||
"skills/taste",
|
||||
"skills/tasteforge-video"
|
||||
],
|
||||
"targets": [
|
||||
"claude",
|
||||
|
||||
@@ -422,6 +422,7 @@
|
||||
"skills/santa-method/",
|
||||
"skills/social-publisher/",
|
||||
"skills/taste/",
|
||||
"skills/tasteforge-video/",
|
||||
"skills/tinystruct-patterns/",
|
||||
"skills/uncloud/",
|
||||
"skills/vite-patterns/",
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
name: tasteforge-video
|
||||
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
|
||||
---
|
||||
|
||||
# TasteForge Video
|
||||
|
||||
TasteForge turns "make it feel like this reference" into a repeatable,
|
||||
inspectable workflow: interview taste, distill it into a structured style
|
||||
pack, validate the pack, apply its measured cadence and look to local media,
|
||||
and export an editable timeline. The canonical implementation is the
|
||||
`tasteforge` package in the Itô video repository; ECC orchestrates and
|
||||
explains it and does not vendor or duplicate its code.
|
||||
|
||||
## When to Use
|
||||
|
||||
- The user asks to **interview for video taste** before any footage is made
|
||||
("ask me about the look", "interview me about aesthetic direction").
|
||||
- The user wants to **distill an aesthetic into structured constraints** — a
|
||||
reusable style pack rather than vibes ("turn these references into a pack").
|
||||
- The user wants to **validate a style pack** (is the metadata complete,
|
||||
schema-valid, cadence measured, spec distilled?).
|
||||
- The user wants to **apply a style pack to local footage** — plan a cut from
|
||||
the pack's measured cadence over local clips, deterministically.
|
||||
- The user wants to **export EDL/FCPXML** — an editable, frame-exact handoff
|
||||
to DaVinci Resolve / Premiere / Final Cut.
|
||||
- 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, multimodal discovery, distill/apply workflows, or a
|
||||
taste interview for video.
|
||||
|
||||
## Local Deterministic Operations vs Provider Generation
|
||||
|
||||
This boundary is the core of the skill. Everything ECC can actually run is
|
||||
**local, deterministic, and offline**:
|
||||
|
||||
| Operation | Deterministic? | ECC may run |
|
||||
|---|---|---|
|
||||
| Taste interview → profile | yes (offline) | yes |
|
||||
| Pack inspect / validate against schemas | yes | yes |
|
||||
| Distill profile (+ measured grounding) → spec | yes (dry-run semantics) | yes |
|
||||
| Apply pack cadence to local media → report + timeline | yes | yes |
|
||||
| Export EDL (CMX3600) / FCPXML 1.9 | yes | yes |
|
||||
| Provenance / lineage report | yes | yes |
|
||||
| Vision-model distillation of stills | **provider generation** | **no** |
|
||||
| Reference-to-video, image-to-3D, hosted compose | **provider generation** | **no** |
|
||||
|
||||
**Provider generation must fail closed in ECC.** Any live Fal (or other
|
||||
provider) call — generating shots, minting prop meshes, hosted VLM
|
||||
distillation — requires explicit separately authorized execution under a
|
||||
separate lane with its own review. ECC never calls Fal, never reads any API
|
||||
key or other credentials (`FAL_KEY` included), uploads no media, and mutates
|
||||
no provider account state. When a request needs provider generation, state
|
||||
exactly that boundary, run the local half (interview, pack validation,
|
||||
planning, export), and stop.
|
||||
|
||||
**Never claim a Fal workflow is saved.** A local reference to a Fal endpoint,
|
||||
model id, or dry-run URL (they appear inside pack metadata) is
|
||||
**reference-only**: it never means a provider-side workflow was saved,
|
||||
persisted, or is authorized to run. Anything produced offline carries
|
||||
dry-run/dry_run semantics — say "dry-run spec" or "deterministic plan", never
|
||||
"generated by the model".
|
||||
|
||||
## Canonical Implementation
|
||||
|
||||
- Repository: `Ito-Markets/ito-video` — find it under the workspace's
|
||||
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`, `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`).
|
||||
- Recovered-source lineage and deliberate exclusions live in the repo's
|
||||
`PROVENANCE.md`. Run `python3 -m tasteforge provenance` for the machine-
|
||||
readable version.
|
||||
|
||||
ECC's job is to route here, run the local deterministic commands, and
|
||||
interpret their JSON — not to reimplement cadence planning, LUT/grade
|
||||
statistics, or timeline emission. If the canonical package is absent, say so
|
||||
and stop; do not reconstruct its logic inline.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Interview** (`interview`): collect answers for the look axes — palette,
|
||||
grain, lighting, focal length, camera motion, subject framing, grade,
|
||||
mood adjectives, avoid list — and separately the content brief. Keep look
|
||||
and content separate; merging them is the classic failure.
|
||||
2. **Distill** (`distill`): map the profile onto the spec schema offline,
|
||||
embedding the pack's measured grounding (black/white point, contrast,
|
||||
per-zone chroma, palette, cut rhythm) when a pack is supplied. The result
|
||||
is a dry-run spec: deterministic, provider `"none"`.
|
||||
3. **Validate** (`validate` / `inspect`): check the pack against its schemas;
|
||||
report errors vs warnings (missing stills in a metadata-only pack are a
|
||||
warning, not an error).
|
||||
4. **Apply** (`apply`): plan shot durations from the pack's measured cadence
|
||||
(seeded, deterministic) over the user's local clips; produce the
|
||||
application report and frame-exact timeline events.
|
||||
5. **Export** (`export`): write CMX3600 EDL + FCPXML 1.9 with rational,
|
||||
NTSC-safe times for import into a real NLE.
|
||||
6. **Audit** (`provenance`): report lineage — recovered-source digests,
|
||||
generation history, fixture provenance, provider references as
|
||||
pointer-only records.
|
||||
|
||||
## File-Driven Multimodal Contract
|
||||
|
||||
Use this path when local references must drive dry-run generation plans for
|
||||
image, video, and 3D-asset outputs while preserving genre separation:
|
||||
|
||||
```bash
|
||||
python3 -m tasteforge multimodal --config workflow.json --out-dir out/multimodal
|
||||
```
|
||||
|
||||
The config names numbered genres and local evidence files. Keep these candidate
|
||||
genres distinct rather than blending them into one generic aesthetic:
|
||||
|
||||
1. Flash Ethereal
|
||||
2. 3D Cyber Glitch
|
||||
3. Fluid Sketch
|
||||
|
||||
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 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. The receipt
|
||||
requires `provider_calls:0` as an exact integer (the JSON boolean `false` is
|
||||
invalid), `provider_execution:false`, and `dry_run:true`. Every genre spec also
|
||||
requires explicit `dry_run:true`. The Resolve effect recipe requires that same
|
||||
exact integer `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: integer `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.
|
||||
|
||||
Treat booleans as invalid numbers everywhere in timeline, evidence, probe, and
|
||||
source-duration data. Every such numeric value must be a finite real: reject
|
||||
`true`, `false`, NaN, infinities, negative event starts, non-positive durations,
|
||||
out-of-range evidence times, and events ending beyond the declared finite
|
||||
positive timeline. Whole-file evidence uses an explicit whole-file time basis
|
||||
and never invents timestamps.
|
||||
|
||||
Receipt references are the duration authority. Key each validated reference
|
||||
duration by its cited SHA-256; duplicate occurrences of one digest must agree
|
||||
on duration or the bundle is invalid. Every effect evidence `source_duration`
|
||||
and every subject-anchor `source_duration` must equal that digest's validated
|
||||
receipt duration, not merely contain its cited time. Probe duration and all
|
||||
probe measurements must describe the same stable bytes used for byte count and
|
||||
SHA-256. If the source mutates while probing or rehashes differently while it
|
||||
is still available, fail closed rather than emitting or accepting a receipt.
|
||||
|
||||
Always run bundle validation after creation. A missing image, video, or 3D-asset
|
||||
manifest must fail closed. Genericized or duplicate genres, periodic schedules,
|
||||
unanchored CV effects, missing placement constraints, provider-execution flags,
|
||||
unbound output files, byte-size drift, or SHA-256 tampering must fail closed.
|
||||
Reject output roots, intermediates, or artifacts that are symlinks, and reject
|
||||
special files (including FIFOs and devices); outputs must remain regular files
|
||||
under a real directory tree. If local `ffmpeg` or `ffprobe` is unavailable, the
|
||||
CLI must return its bounded nonzero local-media-processing error without a
|
||||
Python traceback. Do not repair a failed receipt by deleting evidence or
|
||||
weakening validation.
|
||||
|
||||
## Example Session
|
||||
|
||||
```bash
|
||||
# in the canonical ito-video checkout
|
||||
python3 -m tasteforge validate stylepacks/flashethereal
|
||||
python3 -m tasteforge interview --answers answers.json --genre flashethereal --out profile.json
|
||||
python3 -m tasteforge distill --profile profile.json --pack stylepacks/flashethereal --out spec.json
|
||||
python3 -m tasteforge apply --pack stylepacks/flashethereal --media media.json --duration 20 --out report.json
|
||||
python3 -m tasteforge export --events events.json --out-dir out --title flashethereal-cut
|
||||
python3 -m tasteforge provenance
|
||||
```
|
||||
|
||||
If the user asks for the shots to actually be generated: stop, explain the
|
||||
fail-closed provider boundary, and deliver the deterministic plan, spec, and
|
||||
editable timeline instead.
|
||||
@@ -0,0 +1,446 @@
|
||||
/**
|
||||
* Contract tests for the curated TasteForge video skill.
|
||||
* No test contacts Fal, generates media, or mutates any provider account.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { spawnSync } = require("child_process");
|
||||
|
||||
const REPO_ROOT = path.join(__dirname, "..", "..");
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(REPO_ROOT, relativePath), "utf8");
|
||||
}
|
||||
|
||||
function readJson(relativePath) {
|
||||
return JSON.parse(read(relativePath));
|
||||
}
|
||||
|
||||
function assertExactDryRunBoundary(payload, label) {
|
||||
assert.ok(
|
||||
Number.isInteger(payload.provider_calls) && payload.provider_calls === 0,
|
||||
`${label} must require exact integer 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 assertFiniteReal(value, label, { positive = false, nonnegative = false } = {}) {
|
||||
assert.strictEqual(typeof value, "number", `${label} must be a real number, not a boolean`);
|
||||
assert.ok(Number.isFinite(value), `${label} must be finite`);
|
||||
if (positive) assert.ok(value > 0, `${label} must be positive`);
|
||||
if (nonnegative) assert.ok(value >= 0, `${label} must be nonnegative`);
|
||||
}
|
||||
|
||||
function assertFiniteEvidenceTree(value, label) {
|
||||
if (typeof value === "number" || typeof value === "boolean") {
|
||||
assertFiniteReal(value, label);
|
||||
} else if (Array.isArray(value)) {
|
||||
value.forEach((nested) => assertFiniteEvidenceTree(nested, label));
|
||||
} else if (value && typeof value === "object") {
|
||||
Object.values(value).forEach((nested) => assertFiniteEvidenceTree(nested, label));
|
||||
}
|
||||
}
|
||||
|
||||
function validateFinalContractFixture(fixture) {
|
||||
for (const spec of fixture.genre_specs) {
|
||||
assert.strictEqual(spec.dry_run, true, `genre ${spec.number} must require dry_run:true`);
|
||||
}
|
||||
|
||||
assertExactDryRunBoundary({ ...fixture.receipt, submit: false }, "receipt");
|
||||
const durationByDigest = new Map();
|
||||
for (const reference of fixture.receipt.references) {
|
||||
assertFiniteReal(reference.source_duration, "receipt source_duration", { positive: true });
|
||||
const prior = durationByDigest.get(reference.sha256);
|
||||
assert.ok(
|
||||
prior === undefined || prior === reference.source_duration,
|
||||
"duplicate digest has conflicting source durations"
|
||||
);
|
||||
durationByDigest.set(reference.sha256, reference.source_duration);
|
||||
assertFiniteEvidenceTree(reference.probe, "probe evidence");
|
||||
assert.strictEqual(reference.probe.duration, reference.source_duration);
|
||||
for (const time of [
|
||||
...(reference.probe.sample_times || []),
|
||||
...(reference.probe.scene_changes || []),
|
||||
...(reference.probe.style_samples || []).map((sample) => sample.time),
|
||||
]) {
|
||||
assertFiniteReal(time, "probe evidence time", { nonnegative: true });
|
||||
assert.ok(time <= reference.source_duration, "probe evidence time exceeds source duration");
|
||||
}
|
||||
}
|
||||
|
||||
const recipe = fixture.effect_recipe;
|
||||
assertExactDryRunBoundary({ ...recipe, submit: false }, "effect recipe");
|
||||
assertFiniteReal(recipe.timeline_duration, "timeline duration", { positive: true });
|
||||
for (const event of recipe.events) {
|
||||
assertFiniteReal(event.time, "effect event time", { nonnegative: true });
|
||||
assertFiniteReal(event.duration, "effect event duration", { positive: true });
|
||||
assert.ok(
|
||||
event.time + event.duration <= recipe.timeline_duration,
|
||||
"effect event exceeds timeline duration"
|
||||
);
|
||||
const evidence = event.evidence;
|
||||
const evidenceDuration = durationByDigest.get(evidence.reference_sha256);
|
||||
assert.ok(evidenceDuration !== undefined, "effect evidence cites unknown SHA-256");
|
||||
assert.strictEqual(
|
||||
evidence.source_duration,
|
||||
evidenceDuration,
|
||||
"effect evidence duration must equal receipt reference duration"
|
||||
);
|
||||
assertFiniteReal(evidence.time, "effect evidence time", { nonnegative: true });
|
||||
assert.ok(evidence.time <= evidenceDuration, "effect evidence time exceeds source duration");
|
||||
if (event.requires_subject_anchor) {
|
||||
const anchor = event.subject_anchor;
|
||||
const anchorDuration = durationByDigest.get(anchor.source_ref_sha256);
|
||||
assert.ok(anchorDuration !== undefined, "subject anchor cites unknown SHA-256");
|
||||
assert.strictEqual(
|
||||
anchor.source_duration,
|
||||
anchorDuration,
|
||||
"subject-anchor duration must equal receipt reference duration"
|
||||
);
|
||||
assertFiniteReal(anchor.evidence_time, "subject-anchor evidence time", { nonnegative: true });
|
||||
assert.ok(anchor.evidence_time <= anchorDuration, "anchor evidence time exceeds source duration");
|
||||
assert.strictEqual(anchor.lost_policy, "disable_effect_until_track_recovers");
|
||||
}
|
||||
}
|
||||
|
||||
for (const modality of ["image", "video", "3d_asset"]) {
|
||||
const manifest = fixture.manifests[modality];
|
||||
assert.ok(manifest, `missing ${modality} manifest`);
|
||||
assertExactDryRunBoundary(manifest, `${modality} manifest`);
|
||||
assert.ok(manifest.requests.length > 0, `${modality} requests must not be empty`);
|
||||
for (const request of manifest.requests) {
|
||||
assertExactDryRunBoundary(request, `${modality} request`);
|
||||
assert.strictEqual(request.provider_call_mode, "disabled");
|
||||
}
|
||||
}
|
||||
|
||||
const binding = fixture.source_binding;
|
||||
assert.strictEqual(binding.probed_sha256, binding.before_probe_sha256);
|
||||
assert.strictEqual(binding.receipt_sha256, binding.before_probe_sha256);
|
||||
assert.strictEqual(
|
||||
binding.after_probe_sha256,
|
||||
binding.before_probe_sha256,
|
||||
"source mutation during probe must fail closed"
|
||||
);
|
||||
|
||||
assert.ok(fixture.missing_media_tool_error.exit_code > 0, "missing media tool must exit nonzero");
|
||||
assert.ok(fixture.missing_media_tool_error.stderr.length < 256, "missing-tool error must stay bounded");
|
||||
assert.doesNotMatch(fixture.missing_media_tool_error.stderr, /Traceback/i);
|
||||
|
||||
for (const entry of fixture.output_entries) {
|
||||
assert.ok(
|
||||
entry.type === "regular_file" || entry.type === "directory",
|
||||
`output ${entry.path} must reject symlinks and special files`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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]); }
|
||||
|
||||
test("has valid discoverable frontmatter and trigger phrases", () => {
|
||||
const skill = read("skills/tasteforge-video/SKILL.md");
|
||||
assert.match(
|
||||
skill,
|
||||
/^---\nname: tasteforge-video\ndescription: [^\n]+\nmetadata:\n {2}origin: ECC\n---\n/
|
||||
);
|
||||
for (const trigger of [
|
||||
/interview .*video taste|video .*taste interview/i,
|
||||
/distill .*aesthetic .*structured/i,
|
||||
/validate a style pack/i,
|
||||
/apply a style pack to local footage/i,
|
||||
/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", () => {
|
||||
const skill = read("skills/tasteforge-video/SKILL.md");
|
||||
assert.match(skill, /local, deterministic/i);
|
||||
assert.match(skill, /provider generation/i);
|
||||
assert.match(skill, /must fail closed/i);
|
||||
assert.match(skill, /explicit separately authorized execution/i);
|
||||
assert.match(skill, /ECC never calls Fal/i);
|
||||
assert.match(
|
||||
skill,
|
||||
/never\s+reads\s+any\s+API\s+key\s+or\s+other\s+credentials/i,
|
||||
"skill must state that no API key or credentials are read"
|
||||
);
|
||||
});
|
||||
|
||||
test("never claims a Fal workflow is saved from a local reference", () => {
|
||||
const skill = read("skills/tasteforge-video/SKILL.md");
|
||||
assert.match(
|
||||
skill,
|
||||
/never (?:claim|means|treat)[^.]*provider-side workflow (?:is|was) saved/i
|
||||
);
|
||||
assert.match(skill, /reference[- ]only/i);
|
||||
assert.match(skill, /dry[- ]run|dry_run/i);
|
||||
});
|
||||
|
||||
test("links to the canonical ito-video implementation instead of duplicating it", () => {
|
||||
const skill = read("skills/tasteforge-video/SKILL.md");
|
||||
assert.match(skill, /ito-video/i);
|
||||
assert.match(skill, /Ito-Markets\/ito-video/i);
|
||||
assert.match(skill, /python3 -m tasteforge/);
|
||||
assert.match(skill, /does not (?:vendor|duplicate|copy)/i);
|
||||
});
|
||||
|
||||
test("describes the deterministic workflow surface faithfully", () => {
|
||||
const skill = read("skills/tasteforge-video/SKILL.md");
|
||||
for (const cmd of ["inspect", "validate", "interview", "distill", "apply", "export", "provenance"]) {
|
||||
assert.match(skill, new RegExp(`\\b${cmd}\\b`));
|
||||
}
|
||||
assert.match(skill, /schema/i);
|
||||
assert.match(skill, /cadence/i);
|
||||
assert.match(skill, /style pack/i);
|
||||
});
|
||||
|
||||
test("defines the fail-closed file-driven multimodal contract", () => {
|
||||
const skill = read("skills/tasteforge-video/SKILL.md");
|
||||
assert.match(skill, /python3 -m tasteforge multimodal --config/);
|
||||
for (const phrase of [
|
||||
/Flash Ethereal/,
|
||||
/3D Cyber Glitch/,
|
||||
/Fluid Sketch/,
|
||||
/image.*video.*3D-asset/is,
|
||||
/seeded aperiodic/i,
|
||||
/subject anchor/i,
|
||||
/placement constraints/i,
|
||||
/provider_execution:\s*false/i,
|
||||
/path.*byte size.*SHA-256/is,
|
||||
/genre.*modality/is,
|
||||
/exact reference\/time provenance/i,
|
||||
/provider_calls:\s*0/i,
|
||||
/exact integer/i,
|
||||
/genre spec.*dry_run:\s*true/is,
|
||||
/effect recipe.*provider_calls:\s*0/is,
|
||||
/dry_run:\s*true/i,
|
||||
/submit:\s*false/i,
|
||||
/finite real/i,
|
||||
/booleans as invalid numbers/i,
|
||||
/duplicate occurrences.*digest.*agree.*duration/is,
|
||||
/effect evidence `source_duration`.*validated\s+receipt duration/is,
|
||||
/subject-anchor `source_duration`.*validated\s+receipt duration/is,
|
||||
/same stable bytes/i,
|
||||
/mutates while probing/i,
|
||||
/ffmpeg.*ffprobe.*bounded nonzero.*without a\s+Python traceback/is,
|
||||
/symlinks.*special files/is,
|
||||
/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 fixture enforces the independently passed final contract", () => {
|
||||
const baseline = readJson("tests/fixtures/tasteforge-video/final-contract.json");
|
||||
const clone = () => JSON.parse(JSON.stringify(baseline));
|
||||
assert.doesNotThrow(() => validateFinalContractFixture(clone()));
|
||||
|
||||
const mutations = [
|
||||
["genre dry_run false", (value) => { value.genre_specs[0].dry_run = false; }],
|
||||
["receipt boolean provider_calls", (value) => { value.receipt.provider_calls = false; }],
|
||||
["effect boolean provider_calls", (value) => { value.effect_recipe.provider_calls = false; }],
|
||||
["manifest boolean provider_calls", (value) => { value.manifests.video.provider_calls = false; }],
|
||||
["request boolean provider_calls", (value) => {
|
||||
value.manifests.video.requests[0].provider_calls = false;
|
||||
}],
|
||||
["boolean timeline number", (value) => { value.effect_recipe.events[0].time = false; }],
|
||||
["non-finite event duration", (value) => { value.effect_recipe.events[0].duration = NaN; }],
|
||||
["non-finite evidence duration", (value) => {
|
||||
value.effect_recipe.events[0].evidence.source_duration = Infinity;
|
||||
}],
|
||||
["boolean probe measurement", (value) => {
|
||||
value.receipt.references[0].probe.style_samples[0].luma = false;
|
||||
}],
|
||||
["effect duration not bound to digest", (value) => {
|
||||
value.effect_recipe.events[0].evidence.source_duration = 5;
|
||||
}],
|
||||
["anchor duration not bound to digest", (value) => {
|
||||
value.effect_recipe.events[0].subject_anchor.source_duration = 5;
|
||||
}],
|
||||
["duplicate digest conflicting duration", (value) => {
|
||||
const duplicate = JSON.parse(JSON.stringify(value.receipt.references[0]));
|
||||
duplicate.source_duration = 7;
|
||||
duplicate.probe.duration = 7;
|
||||
value.receipt.references.push(duplicate);
|
||||
}],
|
||||
["source mutation during probe", (value) => {
|
||||
value.source_binding.after_probe_sha256 = "b".repeat(64);
|
||||
}],
|
||||
["missing-tool success exit", (value) => { value.missing_media_tool_error.exit_code = 0; }],
|
||||
["missing-tool traceback", (value) => {
|
||||
value.missing_media_tool_error.stderr = "Traceback (most recent call last): secret\n";
|
||||
}],
|
||||
["symlink output", (value) => {
|
||||
value.output_entries.push({ path: "resolve", type: "symlink" });
|
||||
}],
|
||||
["special-file output", (value) => {
|
||||
value.output_entries.push({ path: "resolve/pipe", type: "fifo" });
|
||||
}],
|
||||
];
|
||||
|
||||
for (const [label, mutate] of mutations) {
|
||||
const fixture = clone();
|
||||
mutate(fixture);
|
||||
assert.throws(
|
||||
() => validateFinalContractFixture(fixture),
|
||||
undefined,
|
||||
`${label} was not rejected`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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");
|
||||
assert.ok(module, "media-generation install module is missing");
|
||||
assert.ok(
|
||||
module.paths.includes("skills/tasteforge-video"),
|
||||
"skills/tasteforge-video missing from media-generation paths"
|
||||
);
|
||||
assert.strictEqual(module.defaultInstall, false);
|
||||
const packed = readJson("package.json").files;
|
||||
assert.ok(
|
||||
packed.includes("skills/tasteforge-video/"),
|
||||
"skills/tasteforge-video/ missing from npm files"
|
||||
);
|
||||
});
|
||||
|
||||
test("is discoverable in the source tree and in a simulated packed artifact", () => {
|
||||
const skillPath = path.join(REPO_ROOT, "skills", "tasteforge-video", "SKILL.md");
|
||||
assert.ok(fs.existsSync(skillPath), "SKILL.md missing in source tree");
|
||||
|
||||
// Packed surface: npm includes the directory; the plugin manifest routes
|
||||
// ./skills/ wholesale; nothing ignores the directory.
|
||||
const npmignore = read(".npmignore");
|
||||
const ignoresSkill = npmignore
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && !line.startsWith("#"))
|
||||
.some((line) => {
|
||||
const normalized = line.replace(/\/+$/, "");
|
||||
return (
|
||||
normalized === "skills" ||
|
||||
normalized === "skills/tasteforge-video" ||
|
||||
normalized === "skills/tasteforge-video/SKILL.md"
|
||||
);
|
||||
});
|
||||
assert.ok(!ignoresSkill, ".npmignore must not exclude the skill");
|
||||
|
||||
const claudePlugin = readJson(".claude-plugin/plugin.json");
|
||||
assert.ok(
|
||||
(claudePlugin.skills || []).includes("./skills/"),
|
||||
"claude plugin skills must route to the root skills/ directory"
|
||||
);
|
||||
|
||||
// Simulated installed layout: the files entry must name the skill dir and
|
||||
// the SKILL.md must exist beneath it with non-empty content.
|
||||
const stat = fs.statSync(path.join(REPO_ROOT, "skills", "tasteforge-video"));
|
||||
assert.ok(stat.isDirectory(), "skill must be a directory");
|
||||
assert.ok(fs.readFileSync(skillPath, "utf8").trim().length > 200, "SKILL.md is empty-ish");
|
||||
});
|
||||
|
||||
test("passes the curated skill validator", () => {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[path.join(REPO_ROOT, "scripts", "ci", "validate-skills.js")],
|
||||
{ encoding: "utf8" }
|
||||
);
|
||||
assert.strictEqual(result.status, 0, `validate-skills failed:\n${result.stdout}\n${result.stderr}`);
|
||||
assert.match(result.stdout + result.stderr, /skill director/i, "validator output unrecognized");
|
||||
});
|
||||
|
||||
// Opt-in slow path: verifies the real npm tarball contents. Enabled with
|
||||
// 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 { 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",
|
||||
});
|
||||
assert.strictEqual(result.status, 0, `npm pack failed:\n${result.stderr}`);
|
||||
assert.match(
|
||||
result.stdout + result.stderr,
|
||||
/skills\/tasteforge-video\/SKILL\.md/,
|
||||
"SKILL.md missing from npm tarball contents"
|
||||
);
|
||||
});
|
||||
|
||||
let failed = 0;
|
||||
let skipped = 0;
|
||||
console.log("\n=== Testing TasteForge video skill ===\n");
|
||||
for (const [name, fn] of tests) {
|
||||
try {
|
||||
const result = fn();
|
||||
if (result?.skipped) {
|
||||
skipped += 1;
|
||||
console.log(` - SKIP ${name}: ${result.skipped}`);
|
||||
continue;
|
||||
}
|
||||
console.log(` ✓ ${name}`);
|
||||
} catch (error) {
|
||||
failed += 1;
|
||||
console.log(` ✗ ${name}`);
|
||||
console.error(` ${error.message}`);
|
||||
}
|
||||
}
|
||||
if (failed) process.exit(1);
|
||||
console.log(`\n${tests.length - failed - skipped}/${tests.length} passed, ${skipped} skipped`);
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"genre_specs": [
|
||||
{
|
||||
"number": 1,
|
||||
"style_fingerprint": "flash-ethereal",
|
||||
"dry_run": true
|
||||
},
|
||||
{
|
||||
"number": 2,
|
||||
"style_fingerprint": "fluid-sketch",
|
||||
"dry_run": true
|
||||
}
|
||||
],
|
||||
"receipt": {
|
||||
"provider_calls": 0,
|
||||
"provider_execution": false,
|
||||
"dry_run": true,
|
||||
"references": [
|
||||
{
|
||||
"sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"source_duration": 6,
|
||||
"probe": {
|
||||
"duration": 6,
|
||||
"sample_times": [0.75, 2.25],
|
||||
"scene_changes": [0.75],
|
||||
"style_samples": [
|
||||
{
|
||||
"time": 0.75,
|
||||
"luma": 0.2,
|
||||
"saturation": 0.4
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"effect_recipe": {
|
||||
"provider_calls": 0,
|
||||
"provider_execution": false,
|
||||
"dry_run": true,
|
||||
"timeline_duration": 6,
|
||||
"events": [
|
||||
{
|
||||
"effect": "cv_subject_glitch",
|
||||
"time": 0.5,
|
||||
"duration": 0.25,
|
||||
"evidence": {
|
||||
"reference_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"time": 0.75,
|
||||
"source_duration": 6
|
||||
},
|
||||
"requires_subject_anchor": true,
|
||||
"subject_anchor": {
|
||||
"source_ref_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"evidence_time": 0.75,
|
||||
"source_duration": 6,
|
||||
"lost_policy": "disable_effect_until_track_recovers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"effect": "flash_bloom",
|
||||
"time": 2,
|
||||
"duration": 0.5,
|
||||
"evidence": {
|
||||
"reference_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"time": 2.25,
|
||||
"source_duration": 6
|
||||
},
|
||||
"requires_subject_anchor": false
|
||||
},
|
||||
{
|
||||
"effect": "ink_bleed",
|
||||
"time": 4.25,
|
||||
"duration": 0.5,
|
||||
"evidence": {
|
||||
"reference_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"time": 0.75,
|
||||
"source_duration": 6
|
||||
},
|
||||
"requires_subject_anchor": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifests": {
|
||||
"image": {
|
||||
"provider_calls": 0,
|
||||
"provider_execution": false,
|
||||
"dry_run": true,
|
||||
"submit": false,
|
||||
"requests": [
|
||||
{
|
||||
"provider_calls": 0,
|
||||
"provider_execution": false,
|
||||
"dry_run": true,
|
||||
"submit": false,
|
||||
"provider_call_mode": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"video": {
|
||||
"provider_calls": 0,
|
||||
"provider_execution": false,
|
||||
"dry_run": true,
|
||||
"submit": false,
|
||||
"requests": [
|
||||
{
|
||||
"provider_calls": 0,
|
||||
"provider_execution": false,
|
||||
"dry_run": true,
|
||||
"submit": false,
|
||||
"provider_call_mode": "disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"3d_asset": {
|
||||
"provider_calls": 0,
|
||||
"provider_execution": false,
|
||||
"dry_run": true,
|
||||
"submit": false,
|
||||
"requests": [
|
||||
{
|
||||
"provider_calls": 0,
|
||||
"provider_execution": false,
|
||||
"dry_run": true,
|
||||
"submit": false,
|
||||
"provider_call_mode": "disabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"source_binding": {
|
||||
"before_probe_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"probed_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"receipt_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"after_probe_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
},
|
||||
"missing_media_tool_error": {
|
||||
"exit_code": 2,
|
||||
"stderr": "ERROR local media processing unavailable\n"
|
||||
},
|
||||
"output_entries": [
|
||||
{
|
||||
"path": "genres/01.json",
|
||||
"type": "regular_file"
|
||||
},
|
||||
{
|
||||
"path": "manifests",
|
||||
"type": "directory"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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