v3.0.0: provenance you can check instead of trust - #1
Merged
Conversation
Addresses the AUDIT-v2 findings. The root cause behind most of them was one pattern: every loop artifact had a single fixed path, and every reference between artifacts was a filename. Both held until someone regenerated a frame, edited a brand-lock mid-project, or ran the loop at the same time as a colleague. BREAKING: the output directory layout changed, and the layout is the public interface. output/critique.json -> output/critiques/round-N/SHOT.critique.json output/prompts/GEN.txt -> output/prompts/round-N/GEN.txt output/prompts/revised-GEN.txt -> output/prompts/round-N/revised-GEN.txt output/generated/SHOT.png -> output/frames/round-N/SHOT.png Old trees still read. Nothing auto-migrates. Added: - run.json, written once per run, hashing every input. Schema at skills/storyboard-architect/templates/run.schema.json - tools/validate_provenance.py, recomputes every recorded hash. Catches a frame regenerated after its critique, a brand-lock edited mid-project, a frame never reviewed, two operators colliding on one shot, skipped rounds. --require-accept is the pipeline stop condition - tools/validate_shots.py, the first instance validator in the repo. shots.json and text-overlays.json had schemas and no way to check a file against them, so every rule lived in a SKILL.md as a checkbox - critique schema 1.1: run_id, round, created_at, image_sha256, prompt_ref, prompt_sha256, brand_lock_sha256, generator, model_version, seed, meta. Required and nullable, so an absent input is recorded rather than omitted - tools/check.sh, one entry point for all 16 checks, called by CI - a --selftest on every validator; validate_critique went from 2 cases to 14 - worked-run example: 2 shots, 2 rounds, real hashes, rendered preview Changed: - the gate has a threshold instead of discretion: 3+ major forces REJECT - revision mode stops on REJECT instead of re-emitting a prompt for a verdict that means no fix path exists - post-level-only shots are recorded in run.json, not just mentioned in chat - shots-to-html.py renders preview.html.tpl, which it previously only claimed to share with the skill, and escapes every substitution - the preview shows a run date and a render date separately; one "Generated" date meant re-rendering restamped the run as today - shots schema 1.2: on_screen_text accepts an array, assets.generated carries sha256/round/prompt_ref/critique_ref - validate_capabilities.py compares adapter prose to the matrix; the "JSON wins" rule was stated in all ten adapters and enforced nowhere - nano-banana aspect_param corrected to aspectRatio, midjourney ceiling to 100, gpt-image to 300 - copy-prompt.py reads revision files, which it could not parse at all - install.sh installs tools/, which the skills cite and never shipped - CI calls check.sh and re-renders the bundled previews with pinned timestamps, failing if a byte moves Fixed: - text_07 in the shotkit-explainer example was unreachable from any shot - an off-palette overlay color in that same example - a broken table row in critique-rubric.md - an empty examples/ directory the preview SKILL.md described as populated - saas-clean.md typography that silently would not parse Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Built a project from scratch the way a first-time user would, ran a 12-shot 3-round project, a legacy v2 tree, hostile content, and a no-dependencies install. Four real defects, all in code added earlier today and none caught by the fixtures. - validate_provenance.py ignored a legacy root critique.json while validate_critique.py and shots-to-html.py both read it. The preview rendered an ACCEPT badge for a shot this tool called unreviewed. All three now share critique_paths() as the single definition of where critiques live. Selftest added. - hash mismatch messages truncated to 12 characters, so a one-character typo in a hand-authored run.json printed two identical-looking hashes and asserted they differed. Both are now printed in full, and the message distinguishes a likely typo from a changed file. - one edited file produced one error per reference to it: editing a brand-lock in a 12-shot project reported the same problem 20 times. Mismatches are now deduped per (path, recorded, actual) and name the places that referenced them. - check.sh failed all 14 checks with the same missing-package error instead of preflighting the two dependencies once. Also softened nothing: a tree with no run.json is still an error rather than a warning, so a pipeline gate cannot pass an unauditable tree. The message now says what to do about it. Verified end to end: 12 shots across 3 rounds with 19 critiques resolves every shot to its latest round with nothing lost, --require-accept flips between 0 and 1 correctly, and a silent re-roll, a mid-project brand-lock edit, and a shot left at REVISE are each caught. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…un exposed Drove all five skills through a real 30-second brief end to end: authored the storyboard from the installed instructions, forged prompts, generated placeholder frames, critiqued all seven shots, ran a revision round, and rendered the client-facing preview. Three findings, one of them the most useful thing this release has surfaced. The big one. The forge documents four hard rules, and Rule 3 (series_lock anchors appear verbatim in every prompt) is the rule that produces visual consistency across a series. Nothing checked it. A careful authoring pass drifted on it in all seven shots, and the worked-run fixture committed earlier today had drifted on it too. Every other validator was green. Paraphrasing an anchor is what writing good prose feels like, which is exactly why a human or a model will do it every time and never notice. - tools/validate_prompts.py: header completeness, generator id, aspect agreement, the max_prompt_words ceiling, shot coverage, duplicate blocks, Rule 1 (no on-screen text copy in a prompt) and Rule 3 (verbatim anchors). The character anchor is a warning, since a shot with no person can omit it, and the message says so. Ten selftest cases. Wired into check.sh and CI, which now run 18 checks. - worked-run fixture prompts rewritten with verbatim anchors, capitalised at sentence starts since the check is case-insensitive, and the run.json and three critique hashes re-derived. - forge SKILL.md Rule 3 now says what verbatim means, shows the exact drift that happens, and says capitalising a sentence start is fine. Its quality bar defers to the validator instead of listing checkboxes. install.sh shipped skills and tools but not brand-packs, while storyboard-architect's documented fallback is to copy brand-packs/_template.md when no brand-lock is given. That path did not exist after an install. Packs now install to ~/.claude/shotkit-brand-packs/ and --uninstall removes all three trees. Verified end to end after the fixes: 7 shots, 2 rounds, all four project validators pass, --require-accept flips 1 then 0, and the rendered preview passes fifteen deliverable checks including picking the round-2 frame for the revised shot and showing both overlays on the two-overlay shot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A feature-branch push matched neither trigger, so CI produced no signal at all until a pull request existed. The first run on this branch confirmed it: the most recent workflow run was still the June push to main. Adds a concurrency group so a PR branch does not run twice per push. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
GitHub is forcing actions/checkout@v4 and actions/setup-python@v5 onto Node 24 already and warning about it on every run. Bumping to v5 and v6 respectively so this is a chosen version rather than a fallback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tagging a heading that still says Unreleased is the same class of drift this release exists to remove. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the defects in
AUDIT-v2.md, an audit of v2.0.0 that found 50 issues: 7 blocking, 28 major, 15 minor.Most of them shared one root cause. Every artifact in the QA loop had a single fixed path, and every reference between artifacts was a filename. Both hold until someone regenerates a frame, edits a brand-lock mid-project, or runs the loop at the same time as a colleague. Then a filename still resolves while the thing behind it has changed, and nothing notices.
Breaking
The output directory layout moved, and the layout is this project's public interface.
output/critique.jsonoutput/critiques/round-N/{shot_id}.critique.jsonoutput/prompts/{generator}.txtoutput/prompts/round-N/{generator}.txtoutput/prompts/revised-{generator}.txtoutput/prompts/round-N/revised-{generator}.txtoutput/generated/{shot_id}.pngoutput/frames/round-N/{shot_id}.pngExisting trees still read: the tools fall back to
generated/and to a rootcritique.json, and critique schema1.0still validates with a warning. Nothing auto-migrates. That needs to be in the release note, not only the changelog.A 12-shot project over 3 rounds used to produce 36 critiques and keep one, and which one depended on review order.
What to review closely
Ranked by how much damage a mistake would do, not by diff size.
tools/_template.py. A hand-rolled template engine, which is a classic place for bugs. Check the escaping and_find_block_end's nesting.tools/validate_provenance.py. The hash-comparison logic is what the audit trail's credibility rests on. If it can be made to pass on a tampered tree, the rest is decoration.critique.schema.jsonat 1.1. Every provenance field is required and nullable, deliberately:nullrecords that an input was genuinely unavailable, a missing key records nothing. Worth arguing about.tools/validate_prompts.pyRule 3. Exact substring matching on series_lock anchors. Strict by design. If it is too strict in practice, it will be found here first.Added
run.json, written once per run, pinning every input by SHA-256. Schema inskills/storyboard-architect/templates/run.schema.json.tools/validate_provenance.py, recomputes every recorded hash. Catches a frame regenerated after its critique, a brand-lock edited mid-project, a frame never reviewed, two operators colliding on one shot, skipped rounds.--require-acceptis the pipeline stop condition as an exit code.tools/validate_shots.py, the first instance validator in the repo.shots.jsonandtext-overlays.jsonhad schemas and no way to check a file against them, so every rule lived in a SKILL.md as a checkbox.tools/validate_prompts.py, enforces the forge's hard rules, including verbatim series_lock anchors.tools/check.sh, one entry point for all 18 checks, called by CI.--selfteston every validator, each constructing failing fixtures and failing if the check does not catch them.The finding worth reading
The forge documents four hard rules. Rule 3, series_lock anchors appear verbatim in every prompt, is the rule that makes shots in a series look like the same room. Nothing checked it.
Driving the pipeline through a real seven-shot brief drifted on it in all seven shots, with every other validator green. The
worked-runfixture committed earlier in this same branch had drifted on it too.environmentwas"minimalist home office, white walls, oak desk, single houseplant"and it got written as "a minimal home office with white walls and an oak desk". That reads better, which is the problem: paraphrasing an anchor is what writing good prose feels like, so it happens every time and nobody notices until six shots show six different rooms.That is now
validate_prompts.py, and it is the reason this branch has three commits after the first one.Verification
--require-acceptreturns 1 then 0, the rendered preview passes 15 deliverable checksNot done
Named here rather than left to be discovered:
run.jsonrecords what was built and reviewed. Who approved it, and when, is recorded nowhere.v2.0.0tag is still published one commit behindmain, and itsLICENSEis the short-form notice rather than the full text.remotion/src/ShotkitExplainer.tsxis deliberately left matching the artifact it produced; both are labelled.storyboard.md.tpluses dotted paths ({{enter.at}}) that_template.pydoes not support, and a single{{on_screen_text_resolved}}that cannot represent a multi-overlay shot. Nothing is broken today because a model renders it, not the Python. It needs a decision about whether that template should be machine-renderable at all.Review note
This branch was written by Claude and its selftests were written by the same author, so they share the same blind spots. The Rule 3 finding is the argument for why a second reader matters: it was invisible to inspection, including its own, and only surfaced by executing the pipeline rather than reading it.