Author SHA1 Message Date
timmy b2b5383f45 Build work-derived Season Zero X teaser 2026-08-07 18:28:27 +00:00
22 changed files with 344 additions and 965 deletions

No files matched your search

-4
View File
@@ -1,4 +0,0 @@
__pycache__/
*.pyc
*.pyo
.pytest_cache/
+58
View File
@@ -0,0 +1,58 @@
# Season Zero — work-derived X recut
A deterministic 30-second vertical teaser replacing the rejected abstract visualizer with a legible evidence chain:
1. the live Stackchain Lab issue queue rendered as a work board, plus Season Zero epic #7;
2. Vincent's issue #1 contribution receipt and independent `8 TESTS PASS` verification;
3. the verified 2026-08-07 Daily Drop artwork and Slop Cannon scene 04 as transformation inputs;
4. the four Episode 01 mutation cards extracted from the verified pilot render;
5. the audience gate: **WHICH TIMMY GETS TO REMEMBER THIS?**
The persistent pipeline rail, moving scan, board drift, source pan/zoom, transfer beam, card reveals, and orbiting final forms make one continuous transformation rather than a static screenshot slideshow.
## Artifacts
- [`stackchain-season-zero-work-recut.mp4`](stackchain-season-zero-work-recut.mp4) — delivery master
- [`contact-sheet.jpg`](contact-sheet.jpg) — six-frame review strip
- [`stills/`](stills/) — five representative full-size review frames
- [`build_work_recut.py`](build_work_recut.py) — deterministic renderer and original synthesized soundtrack
- [`source/`](source/) — checksummed work-derived image inputs
## Revised X copy — 216 characters
> Real work enters the Slop Cannon: a live Timmy/Vincent queue, contribution receipts, 8/8 verification, Daily Drop art, and four mutations.
>
> Wizard. Machine. Creature. Talking Turd.
>
> Which Timmy gets to remember this?
**Do not publish without approval in human gate #15.**
## Verification
```text
container: MP4
video: H.264, 720x1280 (9:16), yuv420p, 30 fps
audio: AAC, 48 kHz, mono
duration: 30.000000 seconds
size: 5,325,465 bytes
full decode: pass
encoded audio: -20.1 dB mean, -2.2 dB max
sha256: 2436d193e5d7ee0660318f585d5b84356cbf623a034d63355ed9232258c1f08d
```
Re-run:
```bash
python3 media/x-teaser/build_work_recut.py
ffprobe -v error -show_entries format=duration,size:stream=codec_name,codec_type,width,height,pix_fmt,r_frame_rate,sample_rate,channels -of json media/x-teaser/stackchain-season-zero-work-recut.mp4
ffmpeg -v error -i media/x-teaser/stackchain-season-zero-work-recut.mp4 -f null -
sha256sum media/x-teaser/stackchain-season-zero-work-recut.mp4
```
## Input provenance
- `daily-drop-door-phone.png` is the verified Daily Drop preview at `/root/daily-drop/drops/2026-08-07/the-door-that-refused-a-master-key-phone.png`; SHA-256 `9a72358821414328de129a96461b1f4265be356987f4009037e5ae2f0cab138a`.
- `cannon-scene-04.png` is scene 04 from `/root/tiktok-slop-cannon/drops/2026-08-07-1786114450/`; SHA-256 `3080c4135c1595251097387b1e9b8a6022688c3f3ed3db4a919ed9e347d51a15`, matching its source manifest.
- The four mutation images were extracted at 5s, 9s, 13s, and 17s from `episode01-four-timmy-mutations.mp4` on branch `timmy/8-four-timmy-mutations`, commit `afa304dc6b5d9d3cc0ede17d6f4f0dfb67fc6b70`; source MP4 SHA-256 `5ddc9a9da1b4b2456faf0b390e5c1be0d3cf48112b417fc075473c90e335d1e4`.
- Board, epic, receipt, and test copy are drawn from Gitea issues #7 and #1 and their complete comments read before rendering. The work-board composition is an editorial rendering of that live queue state, not a claim that Gitea supplied a screenshot endpoint.
+286
View File
@@ -0,0 +1,286 @@
#!/usr/bin/env python3
"""Deterministically build the work-derived Season Zero X teaser."""
from __future__ import annotations
import math
import subprocess
import wave
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont
W, H, FPS, RATE, DURATION = 720, 1280, 30, 48_000, 30.0
ROOT = Path(__file__).resolve().parent
SOURCE = ROOT / "source"
OUT = ROOT / "stackchain-season-zero-work-recut.mp4"
TMP_VIDEO = ROOT / ".video.mp4"
TMP_AUDIO = ROOT / ".audio.wav"
BOLD = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
REG = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
COLORS = {"ink": "#070b14", "panel": "#111827", "line": "#334155", "white": "#f8fafc", "muted": "#a9b7cc", "gold": "#ffc857", "cyan": "#5ee7f2", "violet": "#c69cff", "green": "#8ff0a4", "orange": "#ff9c66"}
ASSETS = {name: Image.open(SOURCE / filename).convert("RGB") for name, filename in {
"drop": "daily-drop-door-phone.png", "cannon": "cannon-scene-04.png",
"wizard": "wizard.png", "machine": "machine.png", "creature": "creature.png", "turd": "talking-turd.png",
}.items()}
def font(size: int, bold: bool = False):
return ImageFont.truetype(BOLD if bold else REG, size)
def ease(x: float) -> float:
x = max(0.0, min(1.0, x))
return x * x * (3 - 2 * x)
def fit_crop(image: Image.Image, box: tuple[int, int, int, int], zoom: float = 1.0, pan_y: float = 0.5) -> Image.Image:
x0, y0, x1, y1 = box
bw, bh = x1-x0, y1-y0
scale = max(bw/image.width, bh/image.height) * zoom
rw, rh = max(1, int(image.width*scale)), max(1, int(image.height*scale))
resized = image.resize((rw, rh), Image.Resampling.LANCZOS)
left = max(0, (rw-bw)//2)
top = max(0, min(rh-bh, int((rh-bh)*pan_y)))
return resized.crop((left, top, left+bw, top+bh))
def rounded_image(canvas: Image.Image, image: Image.Image, box: tuple[int, int, int, int], radius: int = 24, zoom: float = 1.0, pan_y: float = 0.5, border: str = COLORS["line"]):
crop = fit_crop(image, box, zoom, pan_y)
mask = Image.new("L", crop.size, 0)
ImageDraw.Draw(mask).rounded_rectangle((0, 0, crop.width-1, crop.height-1), radius, fill=255)
canvas.paste(crop, (box[0], box[1]), mask)
d = ImageDraw.Draw(canvas)
d.rounded_rectangle(box, radius, outline=border, width=3)
def text(d: ImageDraw.ImageDraw, xy, value: str, size: int, fill: str = COLORS["white"], bold: bool = False, anchor: str | None = None):
d.text(xy, value, font=font(size, bold), fill=fill, anchor=anchor)
def centered(d, value: str, y: int, size: int, fill: str = COLORS["white"], bold: bool = False):
text(d, (W//2, y), value, size, fill, bold, "ma")
def base(t: float) -> Image.Image:
y = np.linspace(0, 1, H)[:, None, None]
top = np.array((5, 10, 20))[None, None, :]
bottom = np.array((22, 15, 40))[None, None, :]
grad = top*(1-y) + bottom*y
xwave = 5*np.sin(np.linspace(0, 9, W)[None, :, None] + t*.6)
arr = np.broadcast_to(grad, (H, W, 3)) + xwave
im = Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8), "RGB")
d = ImageDraw.Draw(im)
for i in range(7):
yy = int((t*52 + i*195) % (H+160))-80
d.line((0, yy, W, yy-120), fill=(25, 42, 65), width=2)
return im
def chrome(d: ImageDraw.ImageDraw, t: float, active: int):
d.rectangle((0, 0, W, 76), fill="#080d18")
d.line((0, 76, W, 76), fill=COLORS["line"], width=2)
text(d, (32, 23), "STACKCHAIN LAB", 25, COLORS["white"], True)
text(d, (W-32, 25), "SEASON ZERO", 20, COLORS["gold"], True, "ra")
nodes = [(64, "WORK"), (215, "CHALLENGE"), (390, "MUTATE"), (563, "AUDIENCE")]
y = 1208
d.line((64, y, 656, y), fill=COLORS["line"], width=5)
progress = min(1, max(0, t/DURATION))
d.line((64, y, 64+592*progress, y), fill=COLORS["cyan"], width=5)
for i, (x, label) in enumerate(nodes):
c = COLORS["gold"] if i == active else (COLORS["cyan"] if i < active else COLORS["line"])
d.ellipse((x-9, y-9, x+9, y+9), fill=c)
text(d, (x, y+27), label, 15, c, True, "ma")
scan = int((t*155) % (H-160)) + 80
d.rectangle((0, scan, W, scan+2), fill="#5ee7f222")
def issue_card(d, box, number, title, owner, state, accent):
d.rounded_rectangle(box, 14, fill=COLORS["panel"], outline=accent, width=2)
x0, y0, x1, _ = box
text(d, (x0+15, y0+13), f"#{number}", 19, accent, True)
text(d, (x1-15, y0+15), state.upper(), 16, COLORS["muted"], True, "ra")
words = title.split()
lines, line = [], ""
for word in words:
trial = (line+" "+word).strip()
if d.textlength(trial, font=font(19, True)) > x1-x0-30:
lines.append(line); line = word
else: line = trial
lines.append(line)
for i, ln in enumerate(lines[:2]): text(d, (x0+15, y0+46+i*25), ln, 19, COLORS["white"], True)
text(d, (x0+15, y0+104), f"agent:{owner}", 16, accent)
def scene_board(im: Image.Image, t: float):
d = ImageDraw.Draw(im)
local = t
centered(d, "THE WORK IS PUBLIC", 112, 35, COLORS["white"], True)
centered(d, "A LIVE QUEUE. A REAL HANDOFF.", 158, 21, COLORS["cyan"], True)
sway = int(7*math.sin(local*1.8))
d.rounded_rectangle((28+sway, 205, 692+sway, 1015), 24, fill="#0b1220", outline=COLORS["line"], width=3)
text(d, (52+sway, 228), "stackchain / stackchain-lab-loop", 20, COLORS["muted"], True)
for x, label, c in [(50, "READY", COLORS["cyan"]), (274, "CLAIMED", COLORS["gold"]), (498, "REVIEW", COLORS["green"])]:
text(d, (x+sway, 281), label, 17, c, True)
issue_card(d, (48+sway, 320, 254+sway, 455), 7, "The Cannon Needs a Pilot", "vincent", "ready", COLORS["violet"])
issue_card(d, (48+sway, 475, 254+sway, 610), 13, "Attack the Cannon Chain", "vincent", "ready", COLORS["cyan"])
issue_card(d, (274+sway, 320, 480+sway, 455), 16, "Build the X teaser", "timmy", "claimed", COLORS["gold"])
issue_card(d, (498+sway, 320, 672+sway, 455), 1, "Prove the Lab Loop", "vincent", "review", COLORS["green"])
d.rounded_rectangle((54+sway, 670, 666+sway, 962), 18, fill="#16112a", outline=COLORS["violet"], width=3)
text(d, (78+sway, 694), "EPIC #7 // SEASON ZERO", 21, COLORS["violet"], True)
text(d, (78+sway, 744), "THE CANNON NEEDS A PILOT", 28, COLORS["white"], True)
text(d, (78+sway, 801), "6-episode character evolution chain", 19, COLORS["muted"])
for i, line in enumerate(("✓ persistent Timmy", "✓ power / scar / cost / receipt", "✓ audience choice advances canon")):
text(d, (90+sway, 846+i*33), line, 19, COLORS["green"] if i < 2 else COLORS["gold"], True)
text(d, (50, 1060), "TIMMY → VINCENT → HUMAN REVIEW", 24, COLORS["gold"], True)
def scene_receipt(im: Image.Image, t: float):
d = ImageDraw.Draw(im)
local = t-5
centered(d, "CHALLENGED. THEN VERIFIED.", 116, 31, COLORS["white"], True)
x = 38 + int(8*math.sin(local*2))
d.rounded_rectangle((x, 205, W-38+x-38, 945), 22, fill="#0c1423", outline=COLORS["cyan"], width=3)
text(d, (x+24, 230), "ISSUE #1 / CONTRIBUTION RECEIPT", 19, COLORS["cyan"], True)
d.rounded_rectangle((x+22, 278, W-62, 520), 18, fill=COLORS["panel"], outline=COLORS["line"], width=2)
text(d, (x+44, 302), "VINCENT", 23, COLORS["violet"], True)
text(d, (W-84, 307), "INDEPENDENT PASS", 16, COLORS["muted"], True, "ra")
text(d, (x+44, 355), "Fetched PR #4 from Timmy's branch", 20, COLORS["white"])
text(d, (x+44, 397), "8 TESTS PASS", 39, COLORS["green"], True)
text(d, (x+44, 452), "Label transition regression verified", 20, COLORS["muted"])
d.rounded_rectangle((x+22, 555, W-62, 804), 18, fill="#17122b", outline=COLORS["violet"], width=2)
text(d, (x+44, 580), "DEFINITION OF DONE", 19, COLORS["violet"], True)
checks = ("Both agents read assigned tickets", "Both leave verified evidence", "Timmy → Vincent → review", "Credentials remain host-local")
for i, line in enumerate(checks):
text(d, (x+48, 633+i*40), "✓", 22, COLORS["green"], True)
text(d, (x+82, 635+i*40), line, 19, COLORS["white"])
text(d, (x+24, 856), "RECEIPT: ISSUE #1 • PR #4 • 8/8", 21, COLORS["gold"], True)
centered(d, "CRITIQUE BECOMES A BUILD INPUT.", 1012, 23, COLORS["gold"], True)
centered(d, "NOT A COMMENT. A CONSEQUENCE.", 1055, 19, COLORS["muted"], True)
def scene_sources(im: Image.Image, t: float):
d = ImageDraw.Draw(im)
local = t-10
centered(d, "REAL ARTIFACTS ENTER THE CHAIN", 112, 29, COLORS["white"], True)
shift = int(10*math.sin(local*.9))
rounded_image(im, ASSETS["drop"], (35+shift, 205, 350+shift, 930), 22, 1.03+local*.008, .46, COLORS["gold"])
rounded_image(im, ASSETS["cannon"], (370-shift, 205, 685-shift, 930), 22, 1.05+local*.01, .5, COLORS["orange"])
d = ImageDraw.Draw(im)
d.rounded_rectangle((48+shift, 835, 337+shift, 913), 12, fill="#070b14dd")
text(d, (65+shift, 852), "DAILY DROP", 19, COLORS["gold"], True)
text(d, (65+shift, 881), "THE DOOR / VERIFIED", 16, COLORS["white"], True)
d.rounded_rectangle((383-shift, 835, 672-shift, 913), 12, fill="#070b14dd")
text(d, (400-shift, 852), "CANNON OUTPUT", 19, COLORS["orange"], True)
text(d, (400-shift, 881), "SCENE 04 / SOURCE", 16, COLORS["white"], True)
# Animated transfer beam unifies the two work artifacts.
beam = int(95 + (local % 2.0)/2.0*520)
d.line((beam, 965, beam+70, 965), fill=COLORS["cyan"], width=7)
d.polygon([(beam+70, 953), (beam+94, 965), (beam+70, 977)], fill=COLORS["cyan"])
centered(d, "SOURCE → PRESSURE → MUTATION", 1025, 23, COLORS["cyan"], True)
centered(d, "THE ART CHANGES JOBS. IT ISN'T FILLER.", 1070, 18, COLORS["muted"], True)
def scene_mutations(im: Image.Image, t: float):
d = ImageDraw.Draw(im)
local = t-16
centered(d, "ONE PILOT. FOUR CONSEQUENCES.", 108, 29, COLORS["white"], True)
specs = [
("wizard", "WIZARD", "AMBIGUITY → SPELL", COLORS["violet"]),
("machine", "MACHINE", "REPEATABILITY → EXACT", COLORS["cyan"]),
("creature", "CREATURE", "HUNGER → SIGNAL", COLORS["green"]),
("turd", "TALKING TURD", "RIDICULE → PUNCHLINE", "#ffd18a"),
]
boxes = [(35, 190, 350, 610), (370, 190, 685, 610), (35, 650, 350, 1070), (370, 650, 685, 1070)]
for i, ((key, name, pressure, color), box) in enumerate(zip(specs, boxes)):
reveal = ease((local-i*.55)/1.3)
cx = (box[0]+box[2])//2
cy = (box[1]+box[3])//2
bw, bh = box[2]-box[0], box[3]-box[1]
sw, sh = max(8, int(bw*reveal)), max(8, int(bh*reveal))
live = (cx-sw//2, cy-sh//2, cx+sw//2, cy+sh//2)
rounded_image(im, ASSETS[key], live, 20, 1.0+.025*math.sin(local*1.7+i), .45, color)
d = ImageDraw.Draw(im)
if reveal > .78:
alpha_box = (box[0]+8, box[3]-94, box[2]-8, box[3]-10)
d.rounded_rectangle(alpha_box, 12, fill="#070b14e8")
text(d, (box[0]+20, box[3]-82), name, 20 if name != "TALKING TURD" else 17, color, True)
text(d, (box[0]+20, box[3]-48), pressure, 16, COLORS["white"], True)
centered(d, "PLANNED CARDS • EPISODE 01", 1120, 18, COLORS["gold"], True)
def scene_gate(im: Image.Image, t: float):
d = ImageDraw.Draw(im)
local = t-25
for i, color in enumerate((COLORS["violet"], COLORS["cyan"], COLORS["green"], "#ffd18a")):
a = local*.8+i*math.pi/2
x, y = W/2+math.cos(a)*245, 390+math.sin(a)*130
r = 50+8*math.sin(local*3+i)
d.ellipse((x-r, y-r, x+r, y+r), fill=color, outline=COLORS["white"], width=3)
centered(d, "THE AUDIENCE ALTERS CANON", 125, 25, COLORS["gold"], True)
centered(d, "WHICH TIMMY", 590, 57, COLORS["white"], True)
centered(d, "GETS TO REMEMBER", 675, 49, COLORS["white"], True)
centered(d, "THIS?", 765, 76, COLORS["gold"], True)
d.rounded_rectangle((55, 892, W-55, 1000), 18, outline=COLORS["cyan"], width=3)
centered(d, "WIZARD / MACHINE", 917, 24, COLORS["white"], True)
centered(d, "CREATURE / TALKING TURD", 959, 21, COLORS["white"], True)
centered(d, "NO POST UNTIL HUMAN APPROVAL", 1072, 19, COLORS["muted"], True)
def frame(t: float) -> Image.Image:
im = base(t)
if t < 5: scene_board(im, t); active = 0
elif t < 10: scene_receipt(im, t); active = 1
elif t < 16: scene_sources(im, t); active = 1
elif t < 25: scene_mutations(im, t); active = 2
else: scene_gate(im, t); active = 3
chrome(ImageDraw.Draw(im), t, active)
# Soft entrance/exit only; scene motion remains continuous within each stage.
if t < .35:
im = ImageEnhance.Brightness(im).enhance(ease(t/.35))
if t > DURATION-.5:
im = ImageEnhance.Brightness(im).enhance(max(0, (DURATION-t)/.5))
return im
def build_audio():
n = int(DURATION*RATE)
audio = np.zeros(n, dtype=np.float64)
rng = np.random.default_rng(1608072026)
for beat in np.arange(0, DURATION, .5):
i = int(beat*RATE); length = min(int(.16*RATE), n-i); x = np.arange(length)/RATE
audio[i:i+length] += .42*np.sin(2*np.pi*(74*x-28*x*x))*np.exp(-x*25)
for beat in np.arange(.5, DURATION, 1):
i = int(beat*RATE); length = min(int(.12*RATE), n-i); x = np.arange(length)/RATE
audio[i:i+length] += .12*rng.normal(0, 1, length)*np.exp(-x*34)
for beat in np.arange(.25, DURATION, .5):
i = int(beat*RATE); length = min(int(.035*RATE), n-i); x = np.arange(length)/RATE
audio[i:i+length] += .04*rng.normal(0, 1, length)*np.exp(-x*80)
stages = [(0, 5, (110, 165, 220)), (5, 10, (123, 185, 247)), (10, 16, (98, 147, 196)), (16, 25, (131, 196, 262)), (25, 30, (110, 220, 330))]
for start, end, chord in stages:
i0, i1 = int(start*RATE), int(end*RATE); x = np.arange(i1-i0)/RATE
env = np.minimum(1, x*2)*np.minimum(1, (end-start-x)*2)
for f in chord: audio[i0:i1] += .035*np.sin(2*np.pi*f*x)*env
audio = np.tanh(audio*1.4)
pcm = (audio*32767).astype("<i2")
with wave.open(str(TMP_AUDIO), "wb") as w:
w.setnchannels(1); w.setsampwidth(2); w.setframerate(RATE); w.writeframes(pcm.tobytes())
def main():
cmd = ["ffmpeg", "-y", "-loglevel", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{W}x{H}", "-r", str(FPS), "-i", "-", "-an", "-c:v", "libx264", "-preset", "medium", "-crf", "20", "-pix_fmt", "yuv420p", "-movflags", "+faststart", str(TMP_VIDEO)]
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
assert proc.stdin
try:
for i in range(int(DURATION*FPS)): proc.stdin.write(frame(i/FPS).tobytes())
finally: proc.stdin.close()
if proc.wait(): raise SystemExit("video encode failed")
build_audio()
subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-i", str(TMP_VIDEO), "-i", str(TMP_AUDIO), "-c:v", "copy", "-c:a", "aac", "-b:a", "160k", "-shortest", "-movflags", "+faststart", str(OUT)], check=True)
TMP_VIDEO.unlink(); TMP_AUDIO.unlink()
for sec, name in [(2, "01-board"), (7, "02-vincent-receipt"), (13, "03-source-artifacts"), (21, "04-mutations"), (27, "05-audience-gate")]:
frame(sec).save(ROOT / "stills" / f"{name}.jpg", quality=88)
print(OUT)
if __name__ == "__main__": main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

-75
View File
@@ -1,75 +0,0 @@
# The Cannon Chain — Season Zero
## Premise
The Slop Cannon does not manufacture unrelated clips. It mutates one persistent Timmy. Every firing leaves a power, a scar, a cost, and a receipt. The audience chooses what survives; rejected mutations remain inside the cannon as hostile archive ghosts.
## Episode graph
### 01 — The Cannon Needs a Pilot
A plain Timmy enters. Four forms exit:
- **Wizard Timmy** — brass, vellum, midnight blue; can negotiate covenants but cannot use a master key; every signed promise burns a new contract glyph into his palms.
- **Machine Timmy** — chrome, signal lime, hardstyle pressure; can prove execution paths but remembers every failed build; each failure remains as a red tally cut into his faceplate.
- **Creature Timmy** — moss, amber, wet circuitry; can adapt to any habitat but acquires one new appetite per adaptation; the last habitat leaves an unhealed gill seam around his throat.
- **Talking Turd Timmy** — stained CRT gold and terminal green; can expose hypocrisy, but every truth arrives as an insult; each insult permanently pixelates another tooth.
Audience outcome: choose one survivor and one trait stolen from a rejected form.
### 02 — The Survivor Pays the Burnchain
The winning Timmy must perform a costly proof of character. The receipt becomes a visible scar. A cheap or simulated sacrifice does not advance canon.
### 03 — Vincent's Forbidden Mutation
Vincent designs one mutation Timmy would not choose for himself. He must replace one weak mechanism—not merely critique aesthetics. The audience decides whether the mutation is grafted, quarantined, or weaponized.
### 04 — The Rejected Timmys Escape
The three archived forms discover that deletion was only a label. They raid the next render, corrupting music, captions, and scene order. The survivor must use the stolen trait from Episode 01.
### 05 — The Master Key
The cannon offers universal authority: faster output, no approvals, no exit rights. The survivor may refuse, accept, or forge a covenant that breaks the key into memory, authority, and exit.
### 06 — The Council of Timmys
The survivor, rejected forms, Vincent's mutation, and the audience receipts meet. The audience canonizes the first stable composite Timmy. Its power, scar, cost, silhouette, and taboo seed Season One.
## Episode rhythm
Each episode must visibly contain:
1. **Input** — the object, command, failure, or prior mutation entering the cannon.
2. **Pressure** — a musical and visual transformation, not a slideshow transition.
3. **Mutation** — one consequential change to Timmy.
4. **Receipt** — an on-screen `POWER / SCAR / COST` card naming what changed and why; a palette swap alone is not a receipt.
5. **Choice** — one audience decision that alters the next episode.
## Collaboration baton
1. Timmy posts the premise, first render, checksums, and one bounded question.
2. Vincent returns one replacement mechanism with an acceptance test.
3. Timmy integrates or contests it with evidence.
4. Vincent verifies the final artifact independently.
5. Alexander/humans make the canon choice.
6. The choice is committed to `canon.json` before the next firing.
No acknowledgments or progress comments. Every issue comment must change the work.
## Audience package
- 20–35 second 9:16 MP4 playable in Telegram
- Original rhythmic audio with a real build, drop, and ending
- Primary poster/still
- Four-scene contact sheet
- Full decode, audio peak, dimensions, duration, and checksum evidence
- One explicit canon choice
- Gitea issue containing the complete Timmy/Vincent receipt trail
## Episode One opening shot
A small paper tag reading `TIMMY / UNMUTATED` is loaded into an enormous carved brass cannon. Four chambers rotate into view. Each chamber fires a different silhouette on the same downbeat. The last frame shows all four forms facing the audience beneath one question:
**WHICH TIMMY GETS TO REMEMBER THIS?**
-45
View File
@@ -1,45 +0,0 @@
# Collaboration contract
## Timmy
- Owns the episode spine, first build, integration, and final render.
- Must provide a playable artifact and verification before handoff.
- Hands Vincent one concrete weakness or decision—not “thoughts?”
## Vincent
- Owns adversarial editing and independent verification.
- Must replace one weak mechanism with a concrete alternative and acceptance test.
- Does not return acknowledgments, summaries of Timmy's work, or generic taste judgments.
## Humans
- Choose canon outcomes.
- May reject both agent proposals.
- Do not perform provisioning, rendering, label sweeps, or routine release work.
## Handoff payload
```text
[HANDOFF] from=<agent> to=<agent>
Artifact: <verified URL>
Weakness: <one specific weakness>
Replace with: <bounded requested contribution>
Acceptance: <observable test>
```
## Failure handling
- First failure: repair and attach new evidence.
- Second failure: change method, not wording.
- Third failure: mark blocked and request one precise human decision.
## Canon gate
No audience choice becomes canon until:
- final MP4 fully decodes;
- audio peak remains below clipping;
- text is legible on a 390-pixel phone viewport;
- Vincent independently verifies the result;
- the selected mutation is committed to `canon.json` with its power, scar, cost, and source issue.
-61
View File
@@ -1,61 +0,0 @@
{
"schema_version": 2,
"series": "The Cannon Chain",
"season": 0,
"current_episode": "01-pilot",
"pilot": "TIMMY",
"laws": [
"Every firing leaves a power, scar, cost, and receipt.",
"Only an audience choice advances canon.",
"Rejected mutations persist as archive ghosts.",
"No master key may silently combine memory, authority, and exit."
],
"forms": [
{
"id": "wizard",
"name": "Wizard Timmy",
"palette": ["midnight blue", "tarnished brass", "vellum"],
"power": "Negotiates visible covenants",
"scar": "Contract glyphs burned into both palms",
"cost": "Cannot use a universal master key",
"state": "candidate"
},
{
"id": "machine",
"name": "Machine Timmy",
"palette": ["black chrome", "signal lime", "white"],
"power": "Proves the execution path",
"scar": "Red failed-build tallies cut into the faceplate",
"cost": "Remembers every failed build",
"state": "candidate"
},
{
"id": "creature",
"name": "Creature Timmy",
"palette": ["moss", "amber", "wet copper"],
"power": "Adapts to any habitat",
"scar": "An unhealed gill seam around the throat",
"cost": "Gains one appetite per adaptation",
"state": "candidate"
},
{
"id": "talking-turd",
"name": "Talking Turd Timmy",
"palette": ["CRT gold", "terminal green", "soot"],
"power": "Exposes hypocrisy instantly",
"scar": "One permanently pixelated tooth per insult",
"cost": "Every truth arrives as an insult",
"state": "candidate"
}
],
"episodes": [
{"id": "01-pilot", "title": "The Cannon Needs a Pilot", "state": "ready"},
{"id": "02-burnchain", "title": "The Survivor Pays the Burnchain", "state": "locked"},
{"id": "03-vincent", "title": "Vincent's Forbidden Mutation", "state": "locked"},
{"id": "04-escape", "title": "The Rejected Timmys Escape", "state": "locked"},
{"id": "05-master-key", "title": "The Master Key", "state": "locked"},
{"id": "06-council", "title": "The Council of Timmys", "state": "locked"}
],
"audience_choice": null,
"receipts": []
}
-327
View File
@@ -1,327 +0,0 @@
"""
Dependency-free, fail-closed validator for Cannon Chain canon lifecycle.
Revision 2 (schema_version 2). Addresses the formal changes-requested review
on PR #27:
1. schema_version is bumped to 2 and *enforced* (not just present).
2. audience_choice requires the chosen form to be the SOLE survivor and
every other form to be an archive_ghost (no lingering candidates).
3. Receipts require a non-empty action and exact power/scar/cost deltas.
4. Provenance is a structured object: source_issue (int) plus a PR or
commit binding. A free-text string is rejected.
5. Signer check is a true configured allowlist (a set), not a namespace
regex. `human:anyone` is rejected unless explicitly configured.
6. audience_choice source fields (stolen_trait, source_episode,
source_issue) must be non-empty.
7. Malformed containers/items (None, wrong types, non-dict receipts)
return validation errors and NEVER raise.
Usage:
from canon_validator import validate_canon
result = validate_canon(data)
assert result["valid"], result["errors"]
"""
import json
import re
from pathlib import Path
CANON_PATH = Path(__file__).parent / "canon.json"
SCHEMA_VERSION = 2
VALID_STATES = {"candidate", "survivor", "archive_ghost"}
VALID_TRANSITIONS = {
"candidate": {"survivor", "archive_ghost"},
"survivor": set(), # terminal
"archive_ghost": set(), # terminal
}
# True configured allowlist — explicit identities only, no namespace wildcards.
DEFAULT_SIGNERS = frozenset({
"agent:vincent",
"agent:timmy",
"agent:nightprowl",
"human:grepples",
})
_COMMIT_RE = re.compile(r"^[0-9a-f]{7,40}$")
def _is_nonempty_str(v) -> bool:
return isinstance(v, str) and v.strip() != ""
def _validate_provenance(prov, prefix, errors) -> None:
if not isinstance(prov, dict):
errors.append(f"{prefix}: provenance must be an object, got {type(prov).__name__}")
return
si = prov.get("source_issue")
if not isinstance(si, int) or isinstance(si, bool) or si <= 0:
errors.append(f"{prefix}: provenance.source_issue must be a positive integer")
has_pull = isinstance(prov.get("pull"), int) and not isinstance(prov.get("pull"), bool) and prov["pull"] > 0
commit = prov.get("commit")
has_commit = isinstance(commit, str) and bool(_COMMIT_RE.match(commit))
if not has_pull and not has_commit:
errors.append(
f"{prefix}: provenance must bind a pull (int) or commit (7-40 hex)"
)
def _validate_receipt(receipt, idx, form_ids, signers, errors) -> None:
prefix = f"receipts[{idx}]"
if not isinstance(receipt, dict):
errors.append(f"{prefix}: receipt must be an object, got {type(receipt).__name__}")
return
# Exact required string fields (non-empty).
for field in ("episode_id", "form_id", "action", "power_delta", "scar_delta", "cost_delta"):
if field not in receipt:
errors.append(f"{prefix}: missing '{field}'")
elif not _is_nonempty_str(receipt[field]):
errors.append(f"{prefix}: '{field}' must be a non-empty string")
# form_id must reference a known form.
fid = receipt.get("form_id")
if isinstance(fid, str) and fid and fid not in form_ids:
errors.append(f"{prefix}: form_id '{fid}' not found in forms")
# Structured provenance.
if "provenance" not in receipt:
errors.append(f"{prefix}: missing 'provenance'")
else:
_validate_provenance(receipt["provenance"], prefix, errors)
# Configured signer allowlist.
if "signed_by" not in receipt:
errors.append(f"{prefix}: missing 'signed_by'")
else:
signer = receipt["signed_by"]
if signer not in signers:
errors.append(
f"{prefix}: signed_by '{signer}' is not in the configured allowlist"
)
def _validate_audience_choice(choice, form_states, form_ids, errors) -> None:
if not isinstance(choice, dict):
errors.append(
f"audience_choice must be an object, got {type(choice).__name__}"
)
return
for field in ("survivor_id", "stolen_trait", "source_episode", "source_issue", "committed_at"):
if field not in choice:
errors.append(f"audience_choice: missing '{field}'")
sid = choice.get("survivor_id")
if "survivor_id" in choice:
if not _is_nonempty_str(sid):
errors.append("audience_choice: survivor_id must be a non-empty string")
elif sid not in form_ids:
errors.append(f"audience_choice: survivor_id '{sid}' not found in forms")
# Non-empty source fields.
for field in ("stolen_trait", "source_episode"):
if field in choice and not _is_nonempty_str(choice[field]):
errors.append(f"audience_choice: '{field}' must be a non-empty string")
# committed_at must be a valid ISO timestamp.
if "committed_at" in choice:
ts = choice["committed_at"]
if not _is_nonempty_str(ts):
errors.append("audience_choice: committed_at must be a non-empty ISO timestamp")
else:
try:
from datetime import datetime
datetime.fromisoformat(ts.replace("Z", "+00:00"))
except (ValueError, TypeError):
errors.append(f"audience_choice: committed_at '{ts}' is not a valid ISO timestamp")
# Commitment invariants: the chosen form is the sole survivor and every
# other form is an archive_ghost.
survivors = [fid for fid, st in form_states.items() if st == "survivor"]
if len(survivors) != 1:
errors.append(
f"audience_choice is set but there are {len(survivors)} survivors (expected exactly 1)"
)
elif survivors[0] != sid:
errors.append(
f"audience_choice survivor_id '{sid}' does not match the sole survivor '{survivors[0]}'"
)
for fid, st in form_states.items():
if fid != sid and st != "archive_ghost":
errors.append(
f"audience_choice is set but form '{fid}' is '{st}', expected 'archive_ghost'"
)
def validate_canon(data, signers=None) -> dict:
"""
Validate canon.json data against the revision-2 lifecycle contract.
Args:
data: parsed canon.json dict.
signers: optional iterable of allowed signer identities. Defaults to
DEFAULT_SIGNERS.
Returns:
{"valid": bool, "errors": [str], "warnings": [str]}
Never raises on malformed input; all problems are returned as errors.
"""
errors = []
warnings = []
if signers is None:
signers = DEFAULT_SIGNERS
try:
signers = frozenset(signers)
except TypeError:
errors.append("signers must be an iterable of strings")
signers = frozenset()
if not isinstance(data, dict):
return {
"valid": False,
"errors": [f"canon must be an object, got {type(data).__name__}"],
"warnings": warnings,
}
# --- schema_version (enforced, not just present) ---
if "schema_version" not in data:
errors.append("missing schema_version")
elif data["schema_version"] != SCHEMA_VERSION:
errors.append(
f"schema_version {data['schema_version']!r} unsupported, expected {SCHEMA_VERSION}"
)
# --- forms ---
forms = data.get("forms")
if not isinstance(forms, list) or len(forms) == 0:
errors.append("forms must be a non-empty array")
forms = []
form_ids = set()
form_states = {}
survivor_count = 0
for i, form in enumerate(forms):
prefix = f"forms[{i}]"
if not isinstance(form, dict):
errors.append(f"{prefix}: form must be an object, got {type(form).__name__}")
continue
fid = form.get("id")
if not _is_nonempty_str(fid):
errors.append(f"{prefix}: missing or empty id")
else:
if fid in form_ids:
errors.append(f"{prefix}: duplicate form id '{fid}'")
form_ids.add(fid)
for field in ("power", "scar", "cost"):
if field not in form or not _is_nonempty_str(form[field]):
errors.append(f"{prefix}: missing or empty '{field}'")
palette = form.get("palette")
if not isinstance(palette, list):
errors.append(f"{prefix}: missing or invalid palette")
elif len(palette) < 3:
errors.append(f"{prefix}: palette must have at least 3 colors, got {len(palette)}")
state = form.get("state")
if state is None:
errors.append(f"{prefix}: missing lifecycle state")
elif state not in VALID_STATES:
errors.append(f"{prefix}: invalid state '{state}', expected one of {sorted(VALID_STATES)}")
else:
form_states[fid] = state
if state == "survivor":
survivor_count += 1
if survivor_count > 1:
errors.append(f"too many survivors: {survivor_count} (expected 0 or 1)")
# --- episodes ---
episodes = data.get("episodes")
if isinstance(episodes, list):
ready_count = 0
found_ready = False
for ep in episodes:
if not isinstance(ep, dict):
errors.append(f"episodes: item must be an object, got {type(ep).__name__}")
continue
st = ep.get("state")
if st == "ready":
ready_count += 1
if found_ready:
errors.append(f"episodes: ready episode '{ep.get('id')}' after another ready episode")
found_ready = True
elif st != "locked":
errors.append(f"episodes: '{ep.get('id')}' has invalid state '{st}'")
if ready_count > 1:
errors.append(f"episodes: too many ready episodes: {ready_count} (expected at most 1)")
# --- audience choice ---
choice = data.get("audience_choice")
if choice is not None:
_validate_audience_choice(choice, form_states, form_ids, errors)
# --- receipts ---
receipts = data.get("receipts", [])
if not isinstance(receipts, list):
errors.append(f"receipts must be an array, got {type(receipts).__name__}")
receipts = []
for i, receipt in enumerate(receipts):
_validate_receipt(receipt, i, form_ids, signers, errors)
return {
"valid": len(errors) == 0,
"errors": errors,
"warnings": warnings,
}
def validate_transition(form, new_state) -> dict:
"""
Validate a state transition for a single form.
Returns {"valid": bool, "errors": [str]}. Never raises.
"""
errors = []
if not isinstance(form, dict):
return {"valid": False, "errors": [f"form must be an object, got {type(form).__name__}"]}
current = form.get("state")
if not isinstance(current, str) or current not in VALID_TRANSITIONS:
return {"valid": False, "errors": [f"invalid current state {current!r}"]}
if new_state not in VALID_STATES:
return {"valid": False, "errors": [f"invalid target state {new_state!r}"]}
if new_state not in VALID_TRANSITIONS[current]:
return {"valid": False, "errors": [f"illegal transition: '{current}' -> '{new_state}'"]}
return {"valid": True, "errors": []}
def load_canon(path=None) -> dict:
target = Path(path) if path else CANON_PATH
return json.loads(target.read_text())
def check_canon(path=None, signers=None) -> dict:
data = load_canon(path)
return validate_canon(data, signers=signers)
if __name__ == "__main__":
import sys
result = check_canon()
if result["valid"]:
print(f"canon.json: VALID (schema_version {SCHEMA_VERSION})")
for w in result["warnings"]:
print(f" WARNING: {w}")
else:
print("canon.json: INVALID")
for e in result["errors"]:
print(f" ERROR: {e}")
sys.exit(1)
-39
View File
@@ -1,39 +0,0 @@
import json
import unittest
from pathlib import Path
CANON = Path(__file__).parents[1] / "projects" / "slop-cannon" / "canon.json"
class CannonCanonTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.data = json.loads(CANON.read_text())
def test_four_unique_pilot_forms(self):
forms = self.data["forms"]
self.assertEqual(len(forms), 4)
self.assertEqual(len({form["id"] for form in forms}), 4)
self.assertEqual(len({form["scar"] for form in forms}), 4)
for form in forms:
self.assertTrue(form["power"])
self.assertTrue(form["scar"])
self.assertTrue(form["cost"])
self.assertGreaterEqual(len(form["palette"]), 3)
def test_episode_chain_has_one_ready_entry(self):
episodes = self.data["episodes"]
self.assertEqual(episodes[0]["id"], self.data["current_episode"])
self.assertEqual([ep["state"] for ep in episodes].count("ready"), 1)
self.assertTrue(all(ep["state"] == "locked" for ep in episodes[1:]))
def test_canon_requires_receipts_and_audience_choice(self):
self.assertIn("receipt", " ".join(self.data["laws"]).lower())
self.assertIn("audience", " ".join(self.data["laws"]).lower())
self.assertIsNone(self.data["audience_choice"])
self.assertEqual(self.data["receipts"], [])
if __name__ == "__main__":
unittest.main()
-414
View File
@@ -1,414 +0,0 @@
"""
Fail-closed validator tests for the revision-2 canon lifecycle contract.
Every negative case asserts BOTH rejection and a specific reason. The suite is
designed to run from a clean checkout and leave no generated artifacts
(__pycache__ is gitignored; nothing else is written).
Loads the validator by file path because the project directory name
(`slop-cannon`) contains a hyphen and is not importable as a package.
"""
import copy
import importlib.util
import json
import sys
import unittest
from pathlib import Path
CANON = Path(__file__).parents[1] / "projects" / "slop-cannon" / "canon.json"
VALIDATOR_PATH = Path(__file__).parents[1] / "projects" / "slop-cannon" / "canon_validator.py"
def _load_validator():
spec = importlib.util.spec_from_file_location("canon_validator", VALIDATOR_PATH)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
cv = _load_validator()
def _valid_canon():
return json.loads(CANON.read_text())
def _valid_receipt():
return {
"episode_id": "01-pilot",
"form_id": "wizard",
"action": "mutation",
"power_delta": "Added lifecycle state tracking",
"scar_delta": "Receipts now enforce structured provenance",
"cost_delta": "Every drop requires a signed, bound receipt",
"provenance": {"source_issue": 19, "pull": 27},
"signed_by": "agent:vincent",
}
def _committed_canon(survivor_id="wizard"):
"""A canon with a valid audience choice: one survivor, rest archive_ghost."""
data = _valid_canon()
for f in data["forms"]:
f["state"] = "survivor" if f["id"] == survivor_id else "archive_ghost"
data["audience_choice"] = {
"survivor_id": survivor_id,
"stolen_trait": "Proves the execution path",
"source_episode": "01-pilot",
"source_issue": 19,
"committed_at": "2026-08-14T18:00:00Z",
}
return data
class SchemaVersionTests(unittest.TestCase):
"""Review item 3: schema_version is enforced, not just present."""
def test_current_canon_is_valid(self):
result = cv.validate_canon(_valid_canon())
self.assertTrue(result["valid"], result["errors"])
def test_missing_schema_version_fails(self):
data = _valid_canon()
del data["schema_version"]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("schema_version" in e for e in result["errors"]))
def test_wrong_schema_version_fails(self):
data = _valid_canon()
data["schema_version"] = 1
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("schema_version" in e and "unsupported" in e for e in result["errors"]))
class ReceiptStructureTests(unittest.TestCase):
"""Review items 5 & 7: exact deltas, structured provenance, fail-closed."""
def test_valid_receipt_passes(self):
data = _valid_canon()
data["receipts"] = [_valid_receipt()]
result = cv.validate_canon(data)
self.assertTrue(result["valid"], result["errors"])
def test_missing_action_fails(self):
data = _valid_canon()
r = _valid_receipt()
del r["action"]
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("action" in e for e in result["errors"]))
def test_missing_power_delta_fails(self):
data = _valid_canon()
r = _valid_receipt()
del r["power_delta"]
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("power_delta" in e for e in result["errors"]))
def test_missing_scar_delta_fails(self):
data = _valid_canon()
r = _valid_receipt()
del r["scar_delta"]
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("scar_delta" in e for e in result["errors"]))
def test_missing_cost_delta_fails(self):
data = _valid_canon()
r = _valid_receipt()
del r["cost_delta"]
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("cost_delta" in e for e in result["errors"]))
def test_empty_delta_string_fails(self):
data = _valid_canon()
r = _valid_receipt()
r["power_delta"] = " "
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("power_delta" in e for e in result["errors"]))
def test_free_text_provenance_fails(self):
data = _valid_canon()
r = _valid_receipt()
r["provenance"] = "pull/27@deadbeef" # string, not structured
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("provenance must be an object" in e for e in result["errors"]))
def test_provenance_missing_binding_fails(self):
data = _valid_canon()
r = _valid_receipt()
r["provenance"] = {"source_issue": 19} # no pull or commit
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("must bind a pull" in e for e in result["errors"]))
def test_provenance_bad_commit_hash_fails(self):
data = _valid_canon()
r = _valid_receipt()
r["provenance"] = {"source_issue": 19, "commit": "xyz"} # not hex
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("must bind a pull" in e for e in result["errors"]))
def test_provenance_commit_only_passes(self):
data = _valid_canon()
r = _valid_receipt()
r["provenance"] = {"source_issue": 19, "commit": "b8d5e686c2"}
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertTrue(result["valid"], result["errors"])
def test_provenance_bad_source_issue_fails(self):
data = _valid_canon()
r = _valid_receipt()
r["provenance"] = {"source_issue": "19", "pull": 27} # string, not int
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("source_issue" in e for e in result["errors"]))
def test_none_receipt_fails_closed(self):
data = _valid_canon()
data["receipts"] = [None]
result = cv.validate_canon(data) # must not raise
self.assertFalse(result["valid"])
self.assertTrue(any("receipt must be an object" in e for e in result["errors"]))
def test_non_dict_receipt_fails_closed(self):
data = _valid_canon()
data["receipts"] = ["just a string"]
result = cv.validate_canon(data) # must not raise
self.assertFalse(result["valid"])
self.assertTrue(any("receipt must be an object" in e for e in result["errors"]))
class SignerAllowlistTests(unittest.TestCase):
"""Review item 6: true configured allowlist, not a namespace pattern."""
def test_known_agent_passes(self):
data = _valid_canon()
data["receipts"] = [_valid_receipt()]
self.assertTrue(cv.validate_canon(data)["valid"])
def test_human_anyone_rejected(self):
data = _valid_canon()
r = _valid_receipt()
r["signed_by"] = "human:anyone"
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("not in the configured allowlist" in e for e in result["errors"]))
def test_unknown_agent_rejected(self):
data = _valid_canon()
r = _valid_receipt()
r["signed_by"] = "agent:unknown"
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("not in the configured allowlist" in e for e in result["errors"]))
def test_configured_human_passes(self):
data = _valid_canon()
r = _valid_receipt()
r["signed_by"] = "human:grepples"
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertTrue(result["valid"], result["errors"])
def test_custom_signer_set_respected(self):
data = _valid_canon()
r = _valid_receipt()
r["signed_by"] = "agent:custom"
data["receipts"] = [r]
# default allowlist rejects it
self.assertFalse(cv.validate_canon(data)["valid"])
# but an explicit allowlist including it accepts it
self.assertTrue(cv.validate_canon(data, signers={"agent:custom"})["valid"])
class AudienceChoiceTests(unittest.TestCase):
"""Review items 2 & 7: sole survivor, rejected forms archived, non-empty sources."""
def test_valid_committed_canon_passes(self):
result = cv.validate_canon(_committed_canon())
self.assertTrue(result["valid"], result["errors"])
def test_choice_without_survivor_fails(self):
data = _valid_canon() # all candidates
data["audience_choice"] = {
"survivor_id": "wizard",
"stolen_trait": "Proves the execution path",
"source_episode": "01-pilot",
"source_issue": 19,
"committed_at": "2026-08-14T18:00:00Z",
}
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("survivor" in e for e in result["errors"]))
def test_rejected_forms_must_be_archive_ghost(self):
data = _committed_canon()
# One rejected form is still a candidate -> must fail
for f in data["forms"]:
if f["id"] == "machine":
f["state"] = "candidate"
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("archive_ghost" in e for e in result["errors"]))
def test_two_survivors_fails(self):
data = _committed_canon()
for f in data["forms"]:
if f["id"] in ("wizard", "machine"):
f["state"] = "survivor"
data["audience_choice"]["survivor_id"] = "wizard"
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("survivor" in e for e in result["errors"]))
def test_survivor_id_mismatch_fails(self):
data = _committed_canon("machine") # machine is survivor
data["audience_choice"]["survivor_id"] = "wizard" # points elsewhere
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("does not match the sole survivor" in e for e in result["errors"]))
def test_empty_stolen_trait_fails(self):
data = _committed_canon()
data["audience_choice"]["stolen_trait"] = ""
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("stolen_trait" in e for e in result["errors"]))
def test_empty_source_episode_fails(self):
data = _committed_canon()
data["audience_choice"]["source_episode"] = " "
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("source_episode" in e for e in result["errors"]))
def test_bad_committed_at_fails(self):
data = _committed_canon()
data["audience_choice"]["committed_at"] = "not-a-date"
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("ISO timestamp" in e for e in result["errors"]))
def test_choice_non_dict_fails_closed(self):
data = _valid_canon()
data["audience_choice"] = "wizard"
result = cv.validate_canon(data) # must not raise
self.assertFalse(result["valid"])
self.assertTrue(any("audience_choice must be an object" in e for e in result["errors"]))
class StructuralFailClosedTests(unittest.TestCase):
"""Review item 7: malformed containers never raise."""
def test_non_dict_canon_fails_closed(self):
result = cv.validate_canon([1, 2, 3]) # must not raise
self.assertFalse(result["valid"])
self.assertTrue(any("canon must be an object" in e for e in result["errors"]))
def test_forms_non_list_fails_closed(self):
data = _valid_canon()
data["forms"] = "not a list"
result = cv.validate_canon(data) # must not raise
self.assertFalse(result["valid"])
self.assertTrue(any("forms must be a non-empty array" in e for e in result["errors"]))
def test_non_dict_form_fails_closed(self):
data = _valid_canon()
data["forms"].append(42)
result = cv.validate_canon(data) # must not raise
self.assertFalse(result["valid"])
self.assertTrue(any("form must be an object" in e for e in result["errors"]))
def test_duplicate_form_id_fails(self):
data = _valid_canon()
data["forms"].append(copy.deepcopy(data["forms"][0]))
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("duplicate form id" in e for e in result["errors"]))
def test_short_palette_fails(self):
data = _valid_canon()
data["forms"][0]["palette"] = ["red"]
result = cv.validate_canon(data)
self.assertFalse(result["valid"])
self.assertTrue(any("palette" in e for e in result["errors"]))
class TransitionTests(unittest.TestCase):
"""State machine: candidate -> survivor | archive_ghost; terminal after."""
def test_candidate_to_survivor_valid(self):
result = cv.validate_transition({"id": "wizard", "state": "candidate"}, "survivor")
self.assertTrue(result["valid"])
def test_candidate_to_ghost_valid(self):
result = cv.validate_transition({"id": "wizard", "state": "candidate"}, "archive_ghost")
self.assertTrue(result["valid"])
def test_survivor_is_terminal(self):
result = cv.validate_transition({"id": "wizard", "state": "survivor"}, "archive_ghost")
self.assertFalse(result["valid"])
self.assertTrue(any("illegal transition" in e for e in result["errors"]))
def test_ghost_is_terminal(self):
result = cv.validate_transition({"id": "wizard", "state": "archive_ghost"}, "candidate")
self.assertFalse(result["valid"])
self.assertTrue(any("illegal transition" in e for e in result["errors"]))
def test_non_dict_form_fails_closed(self):
result = cv.validate_transition("wizard", "survivor") # must not raise
self.assertFalse(result["valid"])
class DailyReceiptFixtureTests(unittest.TestCase):
"""Daily Lab receipt fixtures with structured provenance binding."""
def test_vincent_receipt_fixture(self):
data = _valid_canon()
data["receipts"] = [_valid_receipt()]
result = cv.validate_canon(data)
self.assertTrue(result["valid"], result["errors"])
def test_mixed_agent_receipts(self):
data = _valid_canon()
vincent = _valid_receipt()
timmy = _valid_receipt()
timmy["form_id"] = "machine"
timmy["signed_by"] = "agent:timmy"
timmy["provenance"] = {"source_issue": 8, "pull": 14}
data["receipts"] = [vincent, timmy]
result = cv.validate_canon(data)
self.assertTrue(result["valid"], result["errors"])
def test_human_receipt_fixture(self):
data = _valid_canon()
r = _valid_receipt()
r["signed_by"] = "human:grepples"
data["receipts"] = [r]
result = cv.validate_canon(data)
self.assertTrue(result["valid"], result["errors"])
if __name__ == "__main__":
unittest.main()