Install
$ agentstack add skill-wanshuiyin-aris-movie-director-comic-asset-review-loop ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ● Dynamic code execution Used
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
comic-asset-review-loop — the UPSTREAM ref gate (Phase 1 · S5)
The ref gate the comic side was missing. comic-director's panel_gate reviews composed output; the assembly_gate reviews cross-panel consistency. Neither gates the identity refs that feed both — so without this skill, identity drift is only caught downstream, after credits are spent on a panel bake. This skill is the Layer-2 gate on a SINGLE reusable reference asset (the character sheets / location plates / prop cutouts the ART_BIBLE.md points every bake at), run before any panel composition.
A reusable asset is a one-way door: once a hundred panels condition on duo_canonical_ref_v001.png, you cannot quietly swap it. So the lock must be earned cross-model, never self-certified by the generator. This skill locks an asset only at 准×3 — Claude and Gemini and Codex each independently pass the same review round — or it sends the asset back to be regenerated, or (at the round ceiling) escalates the asset requirement itself back to the outline/storyboard layer. The executor (and the generation model family) never acquit their own ref.
asset (review_status:"pending") ◀── comic-asset-ref-generator (S4: generates, NEVER self-locks)
│
▼
LAYER 1 — STATIC CI GATE (deterministic, zero credits)
check_asset_collisions.py (one canonical owner per filename — "one visual dialect, never two")
+ build asserts: zero-text contract · literal contract · raster-ref 6-field completeness
│ pass
▼
LAYER 2 — VISUAL LOOP (round R = 1 … MAX_REVIEW_ROUNDS=3)
① Claude narrative ‖ ② Gemini visual (primary: identity/iso) ‖ ③ Codex synth (policy/semantic)
each reads the REAL asset file · scores 5 dims (0–5 ints) · flags failure_modes from the fixed vocab
│
▼ Codex synthesises: MIN-fuse per dim (max for inverted) · threshold · per-reviewer unanimity
准×3 ? ── no, a family HARD-FAILS a floor ─▶ comic-asset-ref-generator --regenerate (bump version) ─▶ ① R+1
│ no, third family just ABSENT (e.g. Gemini down) ─▶ re-VOTE (re-collect the missing reviewer; NO re-bake)
│ no (safety_ip= 3**.
- **MAX_REVIEW_ROUNDS** = 3 (hard ceiling). Even if Codex keeps saying `regenerate`, the loop STOPS at 3 and
routes back to outline/storyboard — the *requirement* changes; the gate does not grind an image forever.
- **CROSS-MODEL ACQUITTAL** — Codex is the **synth/policy** reviewer and (in the broader pipeline) the
generation family for raster refs. A Codex `approve` can *diagnose/veto* but is **never the sole acquitter**;
the lock needs Gemini approve + Claude approve too (acceptance-gate.md: the loop DRIVES, it can't ACQUIT).
- **SAFETY_IP SINGLE-VOTE VETO** — `safety_ip /story/ASSET_REVIEW.md` (the human score-progression audit) + wiki nodes/edges
under `wiki/nodes/` + per-reviewer trace under `.aris/traces/comic-asset-review-loop/`.
## Input contract / what this skill owns
The contract boundary is the **`asset` wiki node** authored by [`comic-asset-ref-generator`](../comic-asset-ref-generator/SKILL.md) (S4) at
`review_status: "pending"` — generated, never self-locked. This skill is **pure verify + lock**:
- It **never edits the asset's pixels or geometry** — on `regenerate`/`revise` it routes a ≤140-char
`repair_instruction` back to S4, which bumps the version and emits a new file.
- It **never invents** scores or a verdict on behalf of an absent reviewer — a dim no reviewer scored is
`not_scored` and **EXCLUDED** from the threshold, never substituted with 0 (the v1.0 bug; see Hard do/don't).
- It writes back **only** `status: "locked"` + `payload.review_status: "locked"` on a 准×3 approve, plus the
review/decision/failure_mode trace. `locked` is the one-way door — immutable for the rest of Phase 1.
**Read** the `asset` node payload (per `schemas/node_schema.json`, `node_type: "asset"`,
`payload.required = [asset_kind, name, visual_description, identity_lock, ref_requirements, review_status,
version]`):
- `asset_kind` ∈ {character, scene, prop, text_panel, logo_free_symbol} — **routes the per-reviewer query**
(see §"Asset-kind specialisation").
- `identity_lock.{must_preserve[], must_avoid[]}` — the per-asset rubric (e.g. from `ART_BIBLE.md` §1:
executor = blue hoodie `#1D4684` + brown hair + **no beard**; reviewer = green hoodie `#30582D` + near-black
hair + **beard**; "颜色/胡子/体型轮廓不可变").
- `ref_requirements.{aspect_ratio, background (white|transparent), pose, isolated_reference}`.
- `output_ref.{file_path, data_url, sha256, width, height, mime}` for a raster ref (all 6 fields, else schema
violation) **OR** `{generator_script, owner_script, file_sha256}` for a deterministic SVG/JSON source.
## The EXACT gate (ported from `asset-review-loop`)
### Layer 1 — static CI gate (deterministic, MANDATORY, zero credits)
Run **before** any visual reviewer call (and re-run before lock). Fail-closed; correctness costs no credits.
- **single-source collision check** — `check_asset_collisions.py` static-scans every `gen_*.py` with the regex
`\bw\(\s*["']([^"']+\.(?:svg|json|png))["']` and asserts **each output filename has EXACTLY ONE generator
owner**. `len(writers) > 1` → `exit 1`. This is the tool that enforces **"one visual dialect, never two"**:
two generators writing the same path with different content is a silent, run-order-dependent corruption
(whoever runs last wins). It is deliberately **NOT** a "skip if file exists" guard — that would *hide* the
order bug instead of surfacing it. Verdict: `exit 0` (clean) is a **precondition** for entering Layer 2;
`exit 1` blocks the round and routes to S4 to rename/delete the duplicate. (Verified clean at 26 outputs in
the worked example.)
- **zero-text contract** — for any asset that must contain no glyphs (e.g. the S22 constellation):
`assert " **These are THIS skill's pre-gate routing thresholds (the asset-gate step-3 routing this skill OWNS), and
> they are written to MATCH [`comic-cross-layer-gate`](../comic-cross-layer-gate/SKILL.md)'s `--gate asset`
> LOCK predicate verbatim.** The gate's `LOCKED` condition is `identity_lock_satisfied >= 4 AND ref_quality >= 4
> AND bg_isolation >= 4 AND safety_ip >= 4` **AND** the `准 ×3` same-round cross-model unanimity rule —
> `reuse_readiness` is **ADVISORY** at the gate and does **not** block the lock. The `approve` rule below uses
> exactly those four floors; `reuse_readiness` only feeds `revise`/`regenerate` routing, never blocks the lock.
Codex **MIN-fuses** each dim across the reviewers who scored it (the gate's deterministic rule: **min** per dim,
**max** for an inverted dim, single scorer → use it, NO scorer → `not_scored` **EXCLUDE** — **never 0-substitute,
never average**: averaging `{5,3}`→4 would slip a sub-floor ref past the `>=4` lock; min=3 correctly fails). So
this inline routing can only be **stricter**, never weaker, than `--gate asset`. The legal asset verdict set is
`{approve, regenerate, locked, abandon_shot}` (gate-enforced); the internal `revise` below is an alias that
routes a `regenerate`. Then:
- **`approve`** ← `identity_lock_satisfied >= 4` **AND** `ref_quality >= 4` **AND** `bg_isolation >= 4`
**AND** `safety_ip >= 4` (the four gate floors). **`asset_kind == "character"` override:**
`identity_lock_satisfied == 5` is REQUIRED (a raster character ref locks only at a *perfect* identity score —
4 is not enough for a face hundreds of panels condition on); the other three floors stay `>= 4`. (准×3
same-round unanimity still applies on top — see the LOCK note below.)
- **`regenerate`** ← `identity_lock_satisfied = MAX_REVIEW_ROUNDS` without convergence.
> **Deterministic-SVG / JSON asset branch** (a parametric builder, NOT a raster ref → the visual 5-dim rubric
> does not apply). A deterministic-SVG source **locks iff ALL FOUR hold** (Layer-1 asserts + a 3-dim 0–5 read):
> `single_source_owner == pass` (Layer-1 `check_asset_collisions.py` exit 0 — exactly one generator owns the
> filename) **AND** `render_preview_legible >= 4` (the SVG/JSON rasterizes to a non-empty, on-spec preview PNG —
> a RAW render pre-check, not a vote) **AND** `literal_contract >= 4` (the `_contract` field is honored: literals
> are ascii-tokenizable, the JSON is the single coordinate truth, no eyeball re-layout) **AND**
> `style_family_consistency >= 4` (one visual dialect — palette/stroke/grid match the `ART_BIBLE.md` family).
> Plus the **zero-text / non-gated-text asserts**: a wordless source contains no `` (`assert " in svg`); a labeled source's glyphs are exactly the contract's literals. Same 准×3 same-round unanimity gates
> the SVG lock as the raster lock. (Gemini PRIMARY on `render_preview_legible` + `style_family_consistency`;
> Codex PRIMARY on `literal_contract`; `single_source_owner` is the Layer-1 tool, not a reviewer vote.)
> **LOCK requires 准×3 UNANIMITY, not Codex-synth-approve.** Even on a Codex `approve`, the lock fires **iff**
> `cc_approves AND gemini_approves AND codex_approves` in the **same** round (each reviewer's own 5 scores
> independently clear the per-reviewer thresholds — including the `identity_lock_satisfied == 5` floor for a
> character ref / the four-dim SVG floors for a deterministic source). A Codex `approve` with any reviewer below
> threshold is **DOWNGRADED to `revise`**. (`--lite` is **non-acquitting**: with Gemini absent it may only write
> provisional `status: under_review` + `lite_provisional: true`, and can NEVER satisfy the LOCK predicate or reach P6.) This same-round
> unanimity rule is exactly [`comic-cross-layer-gate`](../comic-cross-layer-gate/SKILL.md)'s `准 ×3` for
> `--gate asset` (this skill is the OWNER of that convention) — NOT "three consecutive rounds".
### Veto rules (hard, non-negotiable)
- **safety_ip = MAX_REVIEW_ROUNDS without 准×3 → ESCALATE the asset *requirement*** to
[`comic-outline-creator`](../comic-outline-creator/SKILL.md) / [`comic-storyboard-creator`](../comic-storyboard-creator/SKILL.md)
(the spec/outline must change; stop re-baking the image). Mutual recursion is bounded by this ceiling.
### Controlled failure-mode vocabulary (the FIXED set reviewers draw tags from)
`face_drift, age_drift, costume_drift, pose_off, multi_subject_leak, halo_artifact, shadow_spill,
non_white_bg, jpeg_artifact, glyph_break, unintended_text, watermark_present, contact_sheet, celeb_likeness,
copyrighted_character, brand_logo_present, collage_grid, low_resolution, melted_anatomy`. Every reject mints a
`failure_mode` wiki node `fail:_` tagged from this set (the `` must match `[a-z0-9_]+` —
strip any stray punctuation before assembling the node_id; maps to `comic-director`'s anatomy/identity vetoes) and routes
its tag into the S4 `repair_instruction`. The node MUST carry the schema-required shape (see the `fail:*`
skeleton in "Wiki node skeletons" below): root `status: "active"` + payload `{layer, affected_shot_ids, active,
repair_pattern}`. Because this is an **upstream asset gate with no shots yet**, `affected_shot_ids` is `[]`
(empty) — never invented; mirror the real `examples/comic_m3_audit/wiki/nodes/fail:*.json` shape.
## Wiki node skeletons (the EXACT shape each minted node MUST carry — validated by `cli/validate_wiki.py`)
Every node this skill writes obeys `schemas/node_schema.json`: the **root** carries `node_id, node_type, title,
status, created_at, payload` (all six required), the `node_id` matches `^(…|review|decision|fail):[a-z0-9_-]+$`,
and the `payload` carries every field in `validate_wiki.py` `PAYLOAD_REQUIRED` for that type. The canonical root
`status` for each runtime type (schema $comment; the real example nodes carry exactly these): `review →
"complete"`, `decision → "final"`, `failure_mode → "active"`. Copy these skeletons verbatim:
```jsonc
// review:* — one per reviewer per round (P1 cc, P2 gemini, P3 codex). node_id: review:__round{R}
// PAYLOAD_REQUIRED["review"] = [target_node_id, reviewer, gate_kind] (review_scores carries the 5 dims)
{ "node_id": "review:duo_canonical_ref_v001_cc_round1", "node_type": "review", "status": "complete",
"title": "CC narrative asset review duo_canonical_ref v1 r1", "created_at": "",
"payload": { "target_node_id": "asset:", "reviewer": "cc", "gate_kind": "asset",
"review_scores": { "identity_lock_satisfied": 5, "ref_quality": 4, "bg_isolation": 4,
"reuse_readiness": 4, "safety_ip": 5 },
"failure_modes_flagged": [], "timed_out": false } }
// decision:* — one per round after the synth+准×3 (P4). node_id: decision:asset__round{R}
// PAYLOAD_REQUIRED["decision"] = [target_node_id, verdict, gate_kind]
{ "node_id": "decision:asset_duo_canonical_ref_v001_round1", "node_type": "decision", "status": "final",
"title": "asset_gate duo_canonical_ref v1 r1 -> locked", "created_at": "",
"payload": { "target_node_id": "asset:", "gate_kind": "asset", "verdict": "approve",
"reasoning": "LOCK 准×3 — identity=5 ref=4 bg=4 safety=5 reuse=4; cc&gemini&codex approve r1",
"repair_instruction": "", "prior_lock_count": 3 } }
// fail:* — only on a reject/abandon_shot (P0 Layer-1 fail, P2 parse failure-mode, P5 reject). node_id: fail:_
// PAYLOAD_REQUIRED["failure_mode"] = [layer, affected_shot_ids, active] (upstream gate → affected_shot_ids: [])
{ "node_id": "fail:duo_canonical_ref_v001_costume_drift", "node_type": "failure_mode", "status": "active",
"title": "asset_gate duo_canonical_ref costume_drift", "created_at": "",
"payload": { "layer": "asset_ref", "affected_shot_ids": [], "active": true,
"repair_pattern": "executor 蓝衣棕发无须 (must_preserve); 当前绿衣漂移 — 重生为 #1D4684 hoodie" } }
After any lock (P6), run python3 cli/validate_wiki.py examples/ — it MUST PASS (root status enum, node_id form, payload invariants). A node missing root status or target_node_id / layer / affected_shot_ids / active is the exact failure the validator catches.
The two fail-closed engine contracts (where they bite here)
This is an upstream gate, so it enforces the preconditions the downstream engine fail-closes on. Do NOT conflate the three SVG fields (per schemas/node_schema.json + the engine): the wiki blueprint.payload.content_svg (top-level on the blueprint payload), the panel_spec.payload.content_blueprint (the panelspec's own field — there is NO content_svg on a panelspec), and the comic.json panel's runtime condition.content_svg (the field the engine reads at spiral_engine.js .condition.content_svg).
- Every panel needs a renderable blueprint SVG. A locked asset is only useful if a panel can condition on
it — so when locking a deterministic SVG/JSON source, verify it rasterizes to a non-empty PNG (a blueprint node would carry it as blueprint.payload.content_svg), because at bake time the engine refuses a comic.json panel whose condition.content_svg is null/absent. A ref that cannot become a content-SVG condition is not "reuse-ready" — score reuse_readiness (and, for an SVG source, render_preview_legible) accordingly. Never condition.content_svg: null.
- A baked figure-panel needs
condition.expected_literals. If the asset is a text_panel / figure
source whose downstream panels bake numbers (e.g. a wandb curve, a verdict stamp, a DDL widget), its lock MUST verify the literal contract holds (the literals are ascii-tokenizable and the JSON _contract forbids eyeball re-layout) — because comic-director's gate refuses to run a baked figure-panel whose comic.json condition.expected_literals is empty. Locking a figure source that cannot supply gateable literals would strand the panel; a scene panel with no audited numbers takes text_mode: "html" instead.
Numbered procedure (per round R, start R = 1, loop until 准×3 or R = 3)
P0 — Resolve + Layer-1 gate. Resolve the t
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: wanshuiyin
- Source: wanshuiyin/ARIS-Movie-Director
- License: MIT
- Homepage: https://wanshuiyin.github.io/ARIS-Movie-Director/comic/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.