open-standards/wai/live-conformance/direction_verify.py
Transaction Science c9cfbe3d08 direction: the framing measure and the interpolations as corpus sections of their own
Boxes at both sides of every size bound and every column and row bound, past
the edges and at the limits of a signed integer; each interpolation mode rising
and falling, with floors below zero. The size control now keeps its position,
so it fails on the size bound alone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 16:04:21 -04:00

1344 lines
51 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Second implementation of extensions/direction.md (`wai.media.direction`), over the corpus
`live-conformance/direction/`.
Written from the extension's text. It shares no code with wai-rs: it reads every record as §2
has it read, with the strict JSON reader of synthesis's second implementation
(synthesis_verify.py), verifies every signature with WAI's strict Ed25519 rule
(claim-conformance/_ed25519.py) and every link hash and frames digest with BLAKE3 written from
its specification (claim-conformance/_blake3.py), runs `wai.generate.spectral` in its own
arithmetic after checking it against that capability's conformance vector, renders every clip
frame and output frame (§9), interpolates every track (§5.2), measures every box (§7), computes
what every element governs (§10) and writes every statement and C2PA statement (§11.1, §12.4)
itself. It reads §13's codes from the extension.
python3 -I direction_verify.py <live-conformance/direction>
It gives every vector of the corpus the outcome, the reason, the place and the statements the
corpus states, and checks that every reason of §13 refuses a vector. It then mutates copies of
the corpus and requires every copy to fail, and drops each of its own checks in turn and
requires the corpus to catch the weakened verifier. Exit status 0 only if all of that holds.
Python 3.8 or later; standard library only.
"""
import copy
import hashlib
import json
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance"))
import synthesis_verify as SV # noqa: E402
import _ed25519 as ED # noqa: E402
from _blake3 import blake3 # noqa: E402
EXTENSION = os.path.join(HERE, "..", "extensions", "direction.md")
GENERATOR = os.path.join(HERE, "..", "wai-rs", "tests", "vectors", "generate")
SIGN_DOMAIN = b"wai:direction\x01" # §2
LINK_DOMAIN = b"wai:direction-link\x01" # §2
FRAMES_DOMAIN = b"wai:direction-frames\x01" # §8.4
SPECTRAL = "wai.generate.spectral"
MAX_INT = (1 << 53) - 1
COORD = 1 << 20
LEVELS = ["pixel", "frame", "shot", "sequence"] # §3
SIZES = [("extreme_wide", 0), ("wide", 250), ("full", 700), ("medium_wide", 1100), ("medium", 1500),
("medium_close", 2200), ("close", 3200), ("extreme_close", 5000)] # §7
ROWS = ["top", "middle", "bottom"]
COLUMNS = ["left", "centre", "right"]
CELLS = [r + "-" + c for r in ROWS for c in COLUMNS]
DESCRIPTION = { # §11.1
"shot": "directed shot: framing, duration and subjects",
"frame": "directed keyframes: framing over time",
"pixel": "directed region: samples held to a reference",
"sequence": "directed sequence: order, cuts and transitions",
}
# The checks this verifier makes, each by name, so the self-test can drop one at a time.
CHECKS = [
"members", "levels", "tracks", "timeline_rule", "signature",
"known", "storyboard_first", "storyboard_link", "signer", "conflict_sequence", "conflict_keys", "consistent",
"check", "layout_count", "layout_camera", "layout_layer", "layout_output",
"layer_held", "layer_size", "layer_regenerate", "frames",
"shot_duration", "shot_subjects", "shot_framing", "key_box", "key_camera", "key_classes", "region",
"seq_order", "seq_cuts", "seq_transitions", "seq_ranges", "governs", "policy",
]
DROPPED = set()
def on(name):
return name not in DROPPED
class Refuse(Exception):
def __init__(self, reason, at):
super().__init__(reason)
self.reason, self.at = reason, at
Bad = SV.Bad
need = SV.need
def sha256(b):
return hashlib.sha256(b).digest()
# ---------------------------------------------------------------- §2 values
def integer(v):
need(isinstance(v, int) and not isinstance(v, bool) and 0 <= v <= MAX_INT)
return v
def int_in(v, lo, hi):
integer(v)
need(lo <= v <= hi)
return v
def array(v, lo, hi):
need(isinstance(v, list) and lo <= len(v) <= hi)
return v
def members(v, names):
if on("members"):
need(isinstance(v, dict) and set(v) == set(names))
else:
need(isinstance(v, dict) and set(names) <= set(v))
return v
def hex32(v):
return SV.hexb(v, 32)
def nullable(v, f):
return None if v is None else f(v)
def signed(v):
"""A signed integer (§2)."""
need(isinstance(v, str) and re.fullmatch(r"0|-?[1-9][0-9]{0,6}", v) is not None)
n = int(v)
need(-COORD <= n <= COORD)
return n
def size_ok(w, h):
need(1 <= w <= 4096 and 1 <= h <= 4096 and w * h <= 1 << 23)
def pic(v):
members(v, ["samples", "width", "height"])
w, h = integer(v["width"]), integer(v["height"])
size_ok(w, h)
return (hex32(v["samples"]), w, h)
def box(v):
members(v, ["x", "y", "w", "h"])
return (signed(v["x"]), signed(v["y"]), int_in(v["w"], 1, COORD), int_in(v["h"], 1, COORD))
def rect(v):
members(v, ["x", "y", "w", "h"])
x, y, w, h = integer(v["x"]), integer(v["y"]), integer(v["w"]), integer(v["h"])
need(w >= 1 and h >= 1)
return (x, y, w, h)
def within(r, w, h):
return r[0] + r[2] <= w and r[1] + r[3] <= h
def classes(v):
size = nullable(v["size"], lambda s: SV.one_of(s, [n for n, _ in SIZES]))
position = nullable(v["position"], lambda s: SV.one_of(s, CELLS))
angle = nullable(v["angle"], SV.term)
return (size, position, angle)
def link_hash(v):
return blake3(LINK_DOMAIN + SV.jcs(v).encode("utf-8"))
def signing_bytes(v):
return SIGN_DOMAIN + SV.jcs({k: x for k, x in v.items() if k != "sig"}).encode("utf-8")
SIG_CACHE = {}
def signature(signer, v, sig):
if not on("signature"):
return None
key = (signer, signing_bytes(v), sig)
if key not in SIG_CACHE:
SIG_CACHE[key] = ED.verify(signer, key[1], sig)
return SIG_CACHE[key]
def read_signed(text, kind, names, fields):
"""§2: read one record, then its signature. `fields` reads the kind's own members."""
try:
v = SV.strict_json(text)
members(v, names)
need(v["kind"] == kind)
signer = hex32(v["signer_pubkey"])
sig = SV.hexb(v["sig"], 64)
SV.date_time(v["issued_at"])
r = fields(v)
except (Bad, KeyError, TypeError, AttributeError, IndexError, ValueError):
raise Refuse("record_malformed", None)
bad = signature(signer, v, sig)
if bad:
raise Refuse(bad, None)
r.update(value=v, signer=signer, link=link_hash(v), kind=kind)
return r
def check_levels(v, want):
if on("levels"):
need(v["levels"] == want)
# ---------------------------------------------------------------- §4–§6 direction records
def storyboard_fields(v):
w, h = integer(v["width"]), integer(v["height"])
size_ok(w, h)
members(v["rate"], ["num", "den"])
rate = (int_in(v["rate"]["num"], 1, COORD), int_in(v["rate"]["den"], 1, COORD))
subjects = []
for s in array(v["subjects"], 0, 16):
members(s, ["views"])
views = []
for x in array(s["views"], 1, 8):
members(x, ["angle", "samples", "width", "height"])
angle = SV.term(x["angle"])
need(all(a != angle for a, _ in views))
views.append((angle, pic({"samples": x["samples"], "width": x["width"], "height": x["height"]})))
subjects.append(views)
shots = []
for s in array(v["shots"], 1, 256):
members(s, ["duration", "subjects", "framing", "reference"])
dur = int_in(s["duration"], 1, 65536)
subs = []
for x in array(s["subjects"], 0, 16):
i = integer(x)
need(i < len(subjects) and i not in subs)
subs.append(i)
framing = []
for k, f in enumerate(array(s["framing"], len(subs), len(subs))):
members(f, ["size", "position", "angle"])
c = classes(f)
need(c[2] is None or any(a == c[2] for a, _ in subjects[subs[k]]))
framing.append(c)
ref = nullable(s["reference"], pic)
shots.append({"duration": dur, "subjects": subs, "framing": framing, "reference": ref})
order = None
if v["order"] is not None:
order = []
for i in array(v["order"], 1, 1024):
need(integer(i) < len(shots))
order.append(i)
check_levels(v, ["shot", "sequence"] if order is not None else ["shot"])
return {"width": w, "height": h, "rate": rate, "subjects": subjects, "shots": shots, "order": order}
def key_fields(k):
members(k, ["t", "interp", "camera", "subjects"])
subs = []
for s in array(k["subjects"], 0, 16):
members(s, ["box", "size", "position", "angle"])
subs.append({"box": nullable(s["box"], box), "classes": classes(s)})
return {"t": integer(k["t"]), "interp": SV.one_of(k["interp"], ["hold", "linear", "ease"]),
"camera": nullable(k["camera"], rect), "subjects": subs}
def entries_fields(v):
out = []
for e in array(v, 1, 256):
members(e, ["shot", "keys"])
shot = integer(e["shot"])
need(all(x["shot"] != shot for x in out))
keys = [key_fields(k) for k in array(e["keys"], 1, 1024)]
need(all(keys[i]["t"] < keys[i + 1]["t"] for i in range(len(keys) - 1)))
n = len(keys[0]["subjects"])
need(all(len(k["subjects"]) == n for k in keys))
if on("tracks"):
for s in range(n):
need(len({k["subjects"][s]["box"] is None for k in keys}) == 1)
need(len({k["camera"] is None for k in keys}) == 1)
out.append({"shot": shot, "keys": keys})
return out
def regions_fields(v):
out = []
for r in array(v, 1, 256):
members(r, ["shot", "from", "to", "rects", "source"])
f, t = integer(r["from"]), integer(r["to"])
need(f < t)
out.append({"shot": integer(r["shot"]), "from": f, "to": t,
"rects": [rect(x) for x in array(r["rects"], 1, 64)], "source": pic(r["source"])})
return out
def keyframes_fields(v):
sb = hex32(v["storyboard"])
entries = nullable(v["keyframes"], entries_fields)
regions = nullable(v["regions"], regions_fields)
need(entries is not None or regions is not None)
check_levels(v, [x for x, on_ in (("pixel", regions is not None), ("frame", entries is not None)) if on_])
return {"storyboard": sb, "entries": entries, "regions": regions}
def transition(v):
need(isinstance(v, dict))
if v.get("kind") == "cut":
members(v, ["kind"])
return 0
need(v.get("kind") == "dissolve")
members(v, ["kind", "frames"])
return int_in(v["frames"], 1, MAX_INT)
def events(v):
out = []
for e in array(v, 1, 1024):
members(e, ["shot", "in", "out", "transition"])
i, o = integer(e["in"]), integer(e["out"])
need(i < o <= 65536)
out.append({"shot": integer(e["shot"]), "in": i, "out": o, "d": transition(e["transition"])})
if on("timeline_rule"):
need(out[0]["d"] == 0)
for i, e in enumerate(out):
nxt = out[i + 1]["d"] if i + 1 < len(out) else 0
need(e["d"] + nxt <= e["out"] - e["in"])
return out
def sequence_fields(v):
sb = hex32(v["storyboard"])
ev = events(v["events"])
check_levels(v, ["sequence"])
return {"storyboard": sb, "events": ev}
READERS = {
"wai-storyboard": (["kind", "levels", "width", "height", "rate", "subjects", "shots", "order", "issued_at",
"signer_pubkey", "sig"], storyboard_fields),
"wai-keyframes": (["kind", "storyboard", "levels", "keyframes", "regions", "issued_at", "signer_pubkey", "sig"],
keyframes_fields),
"wai-sequence": (["kind", "storyboard", "levels", "events", "issued_at", "signer_pubkey", "sig"], sequence_fields),
}
def read_record(text):
try:
kind = SV.strict_json(text).get("kind")
except (Bad, AttributeError):
raise Refuse("record_malformed", None)
if kind not in READERS:
raise Refuse("record_malformed", None)
names, fields = READERS[kind]
return read_signed(text, kind, names, fields)
def levels_of(r):
return r["value"]["levels"]
def sets_sequence(r):
return "sequence" in levels_of(r)
# ---------------------------------------------------------------- §8 the directed output
def source(v):
if v is None:
return None
need(isinstance(v, dict))
g = SV.term(v.get("generator"))
if g == SPECTRAL:
members(v, ["generator", "model", "prompt", "seed", "steps"])
p = v["prompt"]
need(isinstance(p, str) and len(p) <= 256 and all(0x20 <= ord(c) <= 0x7E and c not in '"\\' for c in p))
return {"generator": g, "model": hex32(v["model"]), "prompt": p, "seed": int_in(v["seed"], 0, 2**32 - 1),
"steps": int_in(v["steps"], 1, 64)}
members(v, ["generator", "model"])
return {"generator": g, "model": hex32(v["model"])}
def clip_fields(c):
members(c, ["frames", "plate", "camera", "subjects", "holds"])
n = int_in(c["frames"], 1, 65536)
cam = nullable(c["camera"], lambda a: [rect(x) for x in array(a, n, n)])
subs = []
for s in array(c["subjects"], 0, 16):
members(s, ["subject", "views", "boxes"])
subs.append({"subject": integer(s["subject"]), "views": [integer(x) for x in array(s["views"], n, n)],
"boxes": [box(x) for x in array(s["boxes"], n, n)]})
holds = []
for h in array(c["holds"], 0, 64):
members(h, ["from", "to", "rects", "source"])
f, t = integer(h["from"]), integer(h["to"])
need(f < t <= n)
holds.append({"from": f, "to": t, "rects": [rect(x) for x in array(h["rects"], 1, 64)],
"source": hex32(h["source"])})
return {"frames": n, "plate": nullable(c["plate"], hex32), "camera": cam, "subjects": subs, "holds": holds}
def path(v):
need(isinstance(v, list) and len(v) >= 1)
if v[0] == "sequence":
need(len(v) == 1)
return ("sequence",)
need(len(v) == 2 and v[0] in ("shot", "keyframes", "region"))
return (v[0], integer(v[1]))
def frame_set(v):
out = []
for r in array(v, 0, MAX_INT):
need(isinstance(r, list) and len(r) == 2)
a, b = integer(r[0]), integer(r[1])
need(a < b and (not out or out[-1][1] < a))
out.append((a, b))
return out
def directed_fields(v):
direction = [hex32(x) for x in array(v["direction"], 1, 64)]
need(len(set(direction)) == len(direction))
signer = hex32(v["signer_pubkey"])
directors = [hex32(x) for x in array(v["directors"], 1, 64)]
need(len(set(directors)) == len(directors) and signer not in directors)
check = SV.one_of(v["check"], ["replicate", "attest"])
att = v["attestation"]
if check == "replicate":
need(att is None)
else:
members(att, ["generator", "class"])
att = (SV.term(att["generator"]), SV.term(att["class"]))
layers = []
for x in array(v["layers"], 0, 256):
members(x, ["samples", "width", "height", "source"])
p = pic({"samples": x["samples"], "width": x["width"], "height": x["height"]})
need(all(l["pic"][0] != p[0] for l in layers))
layers.append({"pic": p, "source": source(x["source"])})
clips = [clip_fields(c) for c in array(v["clips"], 1, 256)]
timeline = events(v["timeline"])
o = members(v["output"], ["width", "height", "frames", "digest", "content_hash"])
w, h = integer(o["width"]), integer(o["height"])
size_ok(w, h)
governs = []
for g in array(v["governs"], 0, 1024):
members(g, ["record", "element", "frames"])
governs.append((integer(g["record"]), path(g["element"]), frame_set(g["frames"])))
return {"direction": direction, "directors": directors, "replicate": check == "replicate", "attestation": att,
"layers": layers, "clips": clips, "timeline": timeline, "width": w, "height": h,
"frames": int_in(o["frames"], 1, 1 << 20), "digest": hex32(o["digest"]),
"content_hash": nullable(o["content_hash"], hex32), "governs": governs}
DIRECTED = ["kind", "direction", "directors", "check", "attestation", "layers", "clips", "timeline", "output",
"governs", "issued_at", "signer_pubkey", "sig"]
# ---------------------------------------------------------------- §6.2 the timeline
def starts(ev):
s = []
for i, e in enumerate(ev):
s.append(0 if i == 0 else s[i - 1] + (ev[i - 1]["out"] - ev[i - 1]["in"]) - e["d"])
return s, s[-1] + ev[-1]["out"] - ev[-1]["in"]
def shows(ev, s, f):
return [(i, e["shot"], e["in"] + f - s[i]) for i, e in enumerate(ev) if s[i] <= f < s[i] + e["out"] - e["in"]]
# ---------------------------------------------------------------- §5.2, §7
def interpolate(v0, v1, n, d, mode):
if mode == "hold":
return v0
if mode == "linear":
return v0 + ((v1 - v0) * n) // d
return v0 + ((v1 - v0) * n * n * (3 * d - 2 * n)) // (d * d * d)
def track_value(keys, t):
"""keys: [(t, (x, y, w, h), interp)], ascending."""
k = None
for i, (tk, _, _) in enumerate(keys):
if tk <= t:
k = i
if k is None:
return None
tk, vk, mode = keys[k]
if k + 1 == len(keys):
return tuple(vk)
tn, vn, _ = keys[k + 1]
return tuple(interpolate(vk[m], vn[m], t - tk, tn - tk, mode) for m in range(4))
def size_class(h, fh):
q = (1000 * h) // fh
return [n for n, lo in SIZES if q >= lo][-1]
def position_class(b, fw, fh):
x, y, w, h = b
col = min(2, max(0, (3 * (2 * x + w)) // (2 * fw)))
row = min(2, max(0, (3 * (2 * y + h)) // (2 * fh)))
return ROWS[row] + "-" + COLUMNS[col]
def matches(c, b, angle, sb):
size, position, ang = c
return ((size is None or size == size_class(b[3], sb["height"]))
and (position is None or position == position_class(b, sb["width"], sb["height"]))
and (ang is None or ang == angle))
# ---------------------------------------------------------------- the generator, in its own arithmetic
def fnv(b):
h = 14695981039346656037
for x in b:
h = ((h ^ x) * 1099511628211) & 0xFFFFFFFFFFFFFFFF
return h
def splitmix(state):
state = (state + 0x9E3779B97F4A7C15) & 0xFFFFFFFFFFFFFFFF
z = state
z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & 0xFFFFFFFFFFFFFFFF
z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & 0xFFFFFFFFFFFFFFFF
return state, z ^ (z >> 31)
def tdiv(a, b):
"""Division truncating toward zero."""
q = abs(a) // abs(b)
return q if (a >= 0) == (b >= 0) else -q
def iatan2(dy, dx, t):
if dx == 0:
return 0 if dy == 0 else (t // 4 if dy > 0 else 3 * t // 4)
if dy == 0:
return 0 if dx > 0 else t // 2
steps = [128, 76, 40, 20, 10, 5, 3, 1, 1, 0, 0, 0, 0, 0]
x, y, ang = abs(dx) << 12, abs(dy) << 12, 0
for i, a in enumerate(steps):
if y > 0:
x, y, ang = x + (y >> i), y - (x >> i), ang + a
elif y < 0:
x, y, ang = x - (y >> i), y + (x >> i), ang - a
if dx >= 0 and dy >= 0:
q = ang
elif dx < 0 and dy >= 0:
q = t // 2 - ang
elif dx < 0:
q = t // 2 + ang
else:
q = t - ang
return q % t
def isqrt(n):
import math
return math.isqrt(n)
class Model:
def __init__(self, file_bytes):
m = json.loads(file_bytes)
self.lut = m["sine_lut"]
self.t = m["lut_size"]
self.lut_bits = m.get("lut_bits", 14)
self.freq_max = m["freq_max"]
self.amp_bits = m["amp_bits"]
self.offsets = m["color_offsets"]
self.base = m["base_components"]
self.step = m["step_components"]
self.lexicon = {k: list(v) + [0] * (10 - len(v)) for k, v in m.get("lexicon", {}).items()}
def intent(self, prompt):
toks = re.findall(r"[a-z0-9]+", "".join(c.lower() if "A" <= c <= "Z" else c for c in prompt))
sums, n = [0] * 10, 0
for tok in toks:
if tok in self.lexicon:
sums = [a + b for a, b in zip(sums, self.lexicon[tok])]
n += 1
if n == 0:
return dict(hue=[256, 256, 256], freq=256, orient=0, radial=0, mirror=0, gradient=0, angular=0, spiral=0)
mx = max(max(sums[0:3]), 1)
clamp = lambda v, lo, hi: max(lo, min(hi, v)) # noqa: E731
return dict(hue=[tdiv(sums[c] * 256, mx) for c in range(3)], freq=tdiv(sums[3], n),
orient=clamp(tdiv(sums[4], n), -256, 256), radial=clamp(tdiv(sums[5], n), 0, 256),
mirror=clamp(tdiv(sums[6], n), 0, 256), gradient=clamp(tdiv(sums[7], n), -256, 256),
angular=clamp(tdiv(sums[8], n), 0, 256), spiral=clamp(tdiv(sums[9], n), 0, 256))
def generate(self, prompt, seed, steps, h, w):
k = self.base + steps * self.step
it = self.intent(prompt)
if it["orient"] < 0:
sx, sy = max(256 + it["orient"], 0), 256
else:
sx, sy = 256, max(256 - it["orient"], 0)
fmax = max(tdiv(self.freq_max * it["freq"], 256), 1)
state = fnv(prompt.encode()) ^ seed ^ 0xD1B54A32D192ED03
amax = 1 << self.amp_bits
span = 2 * fmax + 1
thr = [it["radial"], it["mirror"], it["angular"], it["spiral"]]
plane = max(256 - sum(thr), 48)
total = plane + sum(thr)
comps = []
for _ in range(k):
state, d = splitmix(state)
pick = d % total
typ = 0
if pick >= plane:
bound = plane
for i, wgt in enumerate(thr):
bound += wgt
if pick < bound:
typ = i + 1
break
state, d = splitmix(state)
fx = d % span - fmax
state, d = splitmix(state)
fy = d % span - fmax
state, d = splitmix(state)
amp = d % amax + 1
state, d = splitmix(state)
phase = d % self.t
if typ == 1:
comps.append((1, max(abs(fx), 2), 0, amp, phase))
elif typ == 3:
comps.append((3, abs(fx) % 11 + 2, 0, amp, phase))
elif typ == 4:
comps.append((4, abs(fx) % 9 + 2, max(abs(fy), 2), amp, phase))
else:
comps.append((typ, tdiv(fx * sx, 256), tdiv(fy * sy, 256), amp, phase))
t = self.t
cx, cy = w // 2, h // 2
need_r = any(c[0] in (1, 4) for c in comps)
need_th = any(c[0] in (3, 4) for c in comps)
gscale = tdiv((1 << self.lut_bits) * amax * k, 2048 * max(cy, 1))
lut = self.lut
acc = [[0] * (w * h) for _ in range(3)]
geo = []
for y in range(h):
for x in range(w):
dx, dy = x - cx, y - cy
geo.append((x, y, dx, dy, isqrt(dx * dx + dy * dy) if need_r else 0, iatan2(dy, dx, t) if need_th else 0))
for c, off in enumerate(self.offsets):
plane_acc = acc[c]
for p, (x, y, dx, dy, r, th) in enumerate(geo):
a = it["gradient"] * (cy - y) * gscale
for typ, fx, fy, amp, phase in comps:
if typ == 1:
arg = fx * r + phase + off
elif typ == 2:
arg = fx * abs(dx) + fy * abs(dy) + phase + off
elif typ == 3:
arg = fx * th + phase + off
elif typ == 4:
arg = fx * th + fy * r + phase + off
else:
arg = fx * x + fy * y + phase + off
a += amp * lut[arg % t]
plane_acc[p] = a
g0 = min(min(a) for a in acc)
g1 = max(max(a) for a in acc)
rng = max(g1 - g0, 1)
eh = [64 + tdiv(it["hue"][c] * 192, 256) for c in range(3)]
out = bytearray(w * h * 3)
for c in range(3):
for p, v in enumerate(acc[c]):
q = tdiv((v - g0) * 255 + tdiv(rng, 2), rng)
out[p * 3 + c] = max(0, min(255, tdiv(q * eh[c], 256)))
return bytes(out)
MODELS = {}
GENERATED = {}
def model_of(file_bytes):
d = sha256(file_bytes)
if d not in MODELS:
MODELS[d] = Model(file_bytes)
return MODELS[d]
def generated(file_bytes, prompt, seed, steps, w, h):
key = (sha256(file_bytes), prompt, seed, steps, w, h)
if key not in GENERATED:
GENERATED[key] = model_of(file_bytes).generate(prompt, seed, steps, h, w)
return GENERATED[key]
def check_generator():
"""The generator against its conformance vector before anything else."""
with open(os.path.join(GENERATOR, "generate_spectral_model.json"), "rb") as f:
mf = f.read()
with open(os.path.join(GENERATOR, "generate.json")) as f:
g = json.load(f)
with open(os.path.join(GENERATOR, "sha256.json")) as f:
want = json.load(f)["generate"]["sha256"]
px = generated(mf, g["prompt"], g["seed"], g["steps"], g["w"], g["h"])
return sha256(px).hex() == want
# ---------------------------------------------------------------- §9 the render
def render_clip_frame(sb, clip, t, pics):
fw, fh = sb["width"], sb["height"]
out = bytearray(fw * fh * 3)
pw, ph, plate = pics[clip["plate"]]
cam = clip["camera"][t] if clip["camera"] is not None else None
for y in range(fh):
for x in range(fw):
if cam is None:
sx, sy = x, y
else:
sx, sy = cam[0] + x * cam[2] // fw, cam[1] + y * cam[3] // fh
k, j = (y * fw + x) * 3, (sy * pw + sx) * 3
out[k:k + 3] = plate[j:j + 3]
for s in clip["subjects"]:
angle, (dg, vw, vh) = sb["subjects"][s["subject"]][s["views"][t]]
_, _, view = pics[dg]
bx, by, bw, bh = s["boxes"][t]
for y in range(max(by, 0), min(by + bh, fh)):
vy = (y - by) * vh // bh
for x in range(max(bx, 0), min(bx + bw, fw)):
vx = (x - bx) * vw // bw
k, j = (y * fw + x) * 3, (vy * vw + vx) * 3
out[k:k + 3] = view[j:j + 3]
for h in clip["holds"]:
if h["from"] <= t < h["to"]:
_, _, src = pics[h["source"]]
for (rx, ry, rw, rh) in h["rects"]:
for y in range(ry, ry + rh):
k = (y * fw + rx) * 3
out[k:k + rw * 3] = src[k:k + rw * 3]
return bytes(out)
def render_output(sb, d, pics):
clips = [[render_clip_frame(sb, c, t, pics) for t in range(c["frames"])] for c in d["clips"]]
ev = d["timeline"]
s, n = starts(ev)
out = []
for f in range(n):
sh = shows(ev, s, f)
if len(sh) == 1:
_, shot, t = sh[0]
out.append(clips[shot][t])
else:
(_, sa, ta), (i, sb_, tb) = sh
dd = ev[i]["d"]
j = f - s[i] + 1
a, b = clips[sa][ta], clips[sb_][tb]
out.append(bytes((x * (dd + 1 - j) + y * j + (dd + 1) // 2) // (dd + 1) for x, y in zip(a, b)))
return out
RENDERED = {}
def frame_digests(sb, d, pics, key):
if key not in RENDERED:
RENDERED[key] = [sha256(f) for f in render_output(sb, d, pics)]
return RENDERED[key]
# ---------------------------------------------------------------- §10 governance
def elements(r):
if r["kind"] == "wai-storyboard":
e = [("shot", i) for i in range(len(r["shots"]))]
return e + ([("sequence",)] if r["order"] is not None else [])
if r["kind"] == "wai-keyframes":
return ([("keyframes", i) for i in range(len(r["entries"] or []))]
+ [("region", j) for j in range(len(r["regions"] or []))])
return [("sequence",)]
LEVEL = {"shot": "shot", "keyframes": "frame", "region": "pixel", "sequence": "sequence"}
def tracks(entry):
keys = entry["keys"]
n = len(keys[0]["subjects"])
subj = []
for s in range(n):
if keys[0]["subjects"][s]["box"] is None:
subj.append(None)
else:
subj.append([(k["t"], k["subjects"][s]["box"], k["interp"]) for k in keys])
cam = None if keys[0]["camera"] is None else [(k["t"], k["camera"], k["interp"]) for k in keys]
return subj, cam
def keyed(entry, duration):
subj, cam = tracks(entry)
if any(x is not None for x in subj) or cam is not None:
return set(range(entry["keys"][0]["t"], duration))
return {k["t"] for k in entry["keys"] if any(any(c is not None for c in s["classes"]) for s in k["subjects"])}
def runs(frames):
out = []
for f in sorted(frames):
if out and out[-1][1] == f:
out[-1] = (out[-1][0], f + 1)
else:
out.append((f, f + 1))
return out
def governed(sb, d, r, el):
ev = d["timeline"]
s, n = starts(ev)
def showing(pred):
return runs(f for f in range(n) if any(pred(shot, t) for _, shot, t in shows(ev, s, f)))
if el[0] == "shot":
return showing(lambda shot, t: shot == el[1])
if el[0] == "sequence":
return [(0, n)]
if el[0] == "keyframes":
e = r["entries"][el[1]]
ks = keyed(e, sb["shots"][e["shot"]]["duration"])
return showing(lambda shot, t: shot == e["shot"] and t in ks)
g = r["regions"][el[1]]
return showing(lambda shot, t: shot == g["shot"] and g["from"] <= t < g["to"])
# ---------------------------------------------------------------- §12 verification
def consistent(sb, r):
shots = sb["shots"]
if r["kind"] == "wai-keyframes":
for e in r["entries"] or []:
if e["shot"] >= len(shots):
return False
shot = shots[e["shot"]]
for k in e["keys"]:
if k["t"] >= shot["duration"] or len(k["subjects"]) != len(shot["subjects"]):
return False
for i, ks in enumerate(k["subjects"]):
a = ks["classes"][2]
if a is not None and not any(x == a for x, _ in sb["subjects"][shot["subjects"][i]]):
return False
for g in r["regions"] or []:
if g["shot"] >= len(shots) or g["to"] > shots[g["shot"]]["duration"]:
return False
if not all(within(x, sb["width"], sb["height"]) for x in g["rects"]):
return False
if (g["source"][1], g["source"][2]) != (sb["width"], sb["height"]):
return False
if r["kind"] == "wai-sequence":
for e in r["events"]:
if e["shot"] >= len(shots) or e["out"] > shots[e["shot"]]["duration"]:
return False
return True
def layout(sb, d):
layer = {l["pic"][0]: l for l in d["layers"]}
if on("layout_count") and len(d["clips"]) != len(sb["shots"]):
raise Refuse("layout_invalid", ["output"])
for i, c in enumerate(d["clips"]):
at = ["clip", i]
for s in c["subjects"]:
if s["subject"] >= len(sb["subjects"]) or any(v >= len(sb["subjects"][s["subject"]]) for v in s["views"]):
raise Refuse("layout_invalid", at)
if d["replicate"]:
if c["plate"] is None:
raise Refuse("layout_invalid", at)
if c["plate"] not in layer:
if on("layout_layer"):
raise Refuse("layer_missing", at)
else:
_, pw, ph = layer[c["plate"]]["pic"]
if c["camera"] is None and (pw, ph) != (sb["width"], sb["height"]):
raise Refuse("layout_invalid", at)
if on("layout_camera") and c["camera"] is not None and not all(within(x, pw, ph) for x in c["camera"]):
raise Refuse("layout_invalid", at)
for s in c["subjects"]:
for v in sorted(set(s["views"])):
_, (dg, vw, vh) = sb["subjects"][s["subject"]][v]
if dg not in layer:
raise Refuse("layer_missing", at)
if layer[dg]["pic"][1:] != (vw, vh):
raise Refuse("layout_invalid", at)
elif c["plate"] is not None:
raise Refuse("layout_invalid", at)
for h in c["holds"]:
if not all(within(x, sb["width"], sb["height"]) for x in h["rects"]):
raise Refuse("layout_invalid", at)
if d["replicate"]:
if h["source"] not in layer:
raise Refuse("layer_missing", at)
if layer[h["source"]]["pic"][1:] != (sb["width"], sb["height"]):
raise Refuse("layout_invalid", at)
for i, e in enumerate(d["timeline"]):
if e["shot"] >= len(d["clips"]) or e["out"] > d["clips"][e["shot"]]["frames"]:
raise Refuse("layout_invalid", ["timeline", i])
_, n = starts(d["timeline"])
if on("layout_output") and ((d["width"], d["height"]) != (sb["width"], sb["height"]) or d["frames"] != n):
raise Refuse("layout_invalid", ["output"])
def directed_events(r):
if r["kind"] == "wai-storyboard":
return [{"shot": i, "in": 0, "out": r["shots"][i]["duration"], "d": 0} for i in r["order"]]
return r["events"]
def check_element(sb, d, r, el):
if el[0] == "shot":
i = el[1]
shot, clip = sb["shots"][i], d["clips"][i]
if on("shot_duration") and clip["frames"] != shot["duration"]:
raise Refuse("duration_mismatch", ["clip", i])
if [s["subject"] for s in clip["subjects"]] != shot["subjects"]:
if on("shot_subjects"):
raise Refuse("subject_mismatch", ["clip", i])
return
for k, c in enumerate(shot["framing"]):
cs = clip["subjects"][k]
angle = sb["subjects"][cs["subject"]][cs["views"][0]][0]
if on("shot_framing") and not matches(c, cs["boxes"][0], angle, sb):
raise Refuse("framing_mismatch", ["clip", i, 0])
elif el[0] == "keyframes":
e = r["entries"][el[1]]
s = e["shot"]
clip = d["clips"][s]
subj, cam = tracks(e)
for t in sorted(keyed(e, sb["shots"][s]["duration"])):
at = ["clip", s, t]
if t >= clip["frames"]:
raise Refuse("framing_mismatch", at)
for k, tr in enumerate(subj):
if tr is not None and on("key_box") and track_value(tr, t) != tuple(clip["subjects"][k]["boxes"][t]):
raise Refuse("framing_mismatch", at)
if cam is not None and on("key_camera"):
got = None if clip["camera"] is None else tuple(clip["camera"][t])
if got is None or track_value(cam, t) != got:
raise Refuse("framing_mismatch", at)
for key in e["keys"]:
if key["t"] == t:
for k, ks in enumerate(key["subjects"]):
cs = clip["subjects"][k]
angle = sb["subjects"][cs["subject"]][cs["views"][t]][0]
if on("key_classes") and not matches(ks["classes"], cs["boxes"][t], angle, sb):
raise Refuse("framing_mismatch", at)
elif el[0] == "region":
g = r["regions"][el[1]]
want = {"from": g["from"], "to": g["to"], "rects": g["rects"], "source": g["source"][0]}
if on("region") and want not in d["clips"][g["shot"]]["holds"]:
raise Refuse("region_mismatch", ["clip", g["shot"]])
else:
want, got = directed_events(r), d["timeline"]
if on("seq_order") and [e["shot"] for e in want] != [e["shot"] for e in got]:
raise Refuse("order_mismatch", ["timeline"])
sw, _ = starts(want)
sg, _ = starts(got)
for i in range(len(want)):
if on("seq_cuts") and sw[i] != sg[i]:
raise Refuse("cut_mismatch", ["timeline", i])
for i in range(len(want)):
if on("seq_transitions") and want[i]["d"] != got[i]["d"]:
raise Refuse("transition_mismatch", ["timeline", i])
for i in range(len(want)):
if on("seq_ranges") and (want[i]["in"], want[i]["out"]) != (got[i]["in"], got[i]["out"]):
raise Refuse("duration_mismatch", ["timeline", i])
def verify(case, models, pool):
# Step 1: reading.
try:
d = read_signed(case["output"], "wai-directed", DIRECTED, directed_fields)
except Refuse as r:
raise Refuse(r.reason, ["output"])
held = []
for i, t in enumerate(case["records"]):
try:
held.append(read_record(t))
except Refuse as r:
raise Refuse(r.reason, ["record", i])
# Step 2: the direction.
bound, seq_set, keyed_shots = [], False, set()
for k, link in enumerate(d["direction"]):
at = ["direction", k]
rs = [r for r in held if r["link"] == link]
if not rs:
if on("known"):
raise Refuse("record_unknown", at)
continue
r = rs[0]
is_sb = r["kind"] == "wai-storyboard"
if on("storyboard_first") and (k == 0) != is_sb:
raise Refuse("storyboard_mismatch", at)
if on("storyboard_link") and k > 0 and r.get("storyboard") != d["direction"][0]:
raise Refuse("storyboard_mismatch", at)
if on("signer") and r["signer"] not in d["directors"]:
raise Refuse("signer_not_party", at)
if sets_sequence(r):
if seq_set and on("conflict_sequence"):
raise Refuse("direction_conflict", at)
seq_set = True
if r["kind"] == "wai-keyframes":
for e in r["entries"] or []:
if e["shot"] in keyed_shots and on("conflict_keys"):
raise Refuse("direction_conflict", at)
keyed_shots.add(e["shot"])
if k > 0 and on("consistent") and not consistent(bound[0], r):
raise Refuse("direction_inconsistent", at)
bound.append(r)
sb = bound[0]
# Step 3: the check.
if on("check") and d["replicate"] and any(l["source"] is not None and l["source"]["generator"] != SPECTRAL
for l in d["layers"]):
raise Refuse("check_mismatch", ["output"])
# Step 4: the layout.
layout(sb, d)
# Steps 5 and 6: the layers and the frames.
statuses, frame_sha = [], None
if d["replicate"]:
pics = {}
for i, l in enumerate(d["layers"]):
at = ["layer", i]
dg, w, h = l["pic"]
src = l["source"]
if src is not None and src["generator"] == SPECTRAL:
mf = models.get(src["model"])
if mf is None or sha256(mf) != src["model"]:
raise Refuse("layer_missing", at)
px = generated(mf, src["prompt"], src["seed"], src["steps"], w, h)
if on("layer_regenerate") and sha256(px) != dg:
raise Refuse("regeneration_mismatch", at)
statuses.append("regenerated")
elif src is None:
px = pool.get(dg) if dg in case["pictures"] else None
if px is None:
if on("layer_held"):
raise Refuse("layer_missing", at)
px = bytes(w * h * 3)
if on("layer_size") and len(px) != w * h * 3:
raise Refuse("samples_mismatch", at)
statuses.append("held")
else:
raise Refuse("layer_missing", at) # another generator's picture: never regenerated here
pics[dg] = (w, h, px)
key = (SV.jcs(d["value"]["clips"]), SV.jcs(d["value"]["timeline"]), sb["link"],
tuple(sorted((k, sha256(v[2])) for k, v in pics.items())))
try:
digests = frame_digests(sb, d, pics, key)
except (IndexError, KeyError, ValueError):
raise Refuse("frame_mismatch", ["output"])
if on("frames") and blake3(FRAMES_DOMAIN + b"".join(digests)) != d["digest"]:
raise Refuse("frame_mismatch", ["output"])
frame_sha = [x.hex() for x in digests]
else:
statuses = ["stated"] * len(d["layers"])
# Step 7: the direction against the layout.
for r in bound:
for el in elements(r):
check_element(sb, d, r, el)
# Step 8: governance.
computed = []
for k, r in enumerate(bound):
for el in elements(r):
computed.append((k, el, governed(sb, d, r, el)))
if on("governs"):
for i in range(max(len(computed), len(d["governs"]))):
got = d["governs"][i] if i < len(d["governs"]) else None
want = computed[i] if i < len(computed) else None
if got != want:
raise Refuse("governance_mismatch", ["governs", i])
# Step 9: policy.
if on("policy") and case["policy"]["require"] == "exact" and not d["replicate"]:
raise Refuse("not_exact", ["policy"])
return statements(d, bound, computed, frame_sha, statuses)
def statements(d, bound, computed, frame_sha, statuses):
def director(r):
return d["directors"].index(r["signer"])
ev = d["timeline"]
s, n = starts(ev)
def el_json(el):
return list(el)
frames = []
for f in range(n):
by = {lv: set() for lv in LEVELS}
for k, el, fr in computed:
if any(a <= f < b for a, b in fr):
by[LEVEL[el[0]]].add(director(bound[k]))
frames.append({"shows": [[shot, t] for _, shot, t in shows(ev, s, f)],
"directed_by": {lv: sorted(by[lv]) for lv in LEVELS}})
c2pa = []
for di in range(len(d["directors"])):
actions = []
for k, el, fr in computed:
r = bound[k]
if director(r) != di or not fr:
continue
changes = []
for a, b in fr:
fr_range = {"type": "frame", "frame": {"start": a, "end": b - 1}}
if el[0] == "region":
for (x, y, w, h) in r["regions"][el[1]]["rects"]:
shape = {"type": "spatial", "shape": {"type": "rectangle", "unit": "pixel",
"origin": {"x": x, "y": y}, "width": w, "height": h}}
changes.append({"region": [fr_range, shape]})
else:
changes.append({"region": [fr_range]})
actions.append({"action": "c2pa.edited", "changes": changes, "description": DESCRIPTION[LEVEL[el[0]]]})
if actions:
c2pa.append({"director": di, "statement": SV.jcs({"actions": actions})})
att = d["attestation"]
return {
"output": d["link"].hex(),
"status": "exact" if d["replicate"] else "attested",
"generator": att[0] if att else None,
"class": att[1] if att else None,
"records": [{"kind": r["kind"], "link": r["link"].hex(), "director": director(r), "levels": levels_of(r)}
for r in bound],
"elements": [{"record": k, "element": el_json(el), "level": LEVEL[el[0]], "director": director(bound[k]),
"frames": [[a, b] for a, b in fr]} for k, el, fr in computed],
"cuts": s[1:],
"frames": frames,
"frame_sha256": frame_sha,
"layers": statuses,
"c2pa": c2pa,
}
# ---------------------------------------------------------------- the corpus
def outcome(v, models, pool):
try:
st = verify(v, models, pool)
return {"outcome": "accept", "statements": st}
except Refuse as r:
return {"outcome": "refuse", "reason": r.reason, "at": r.at}
except Exception as e: # only a verifier with a check dropped gets here
if not DROPPED:
raise
return {"outcome": "error", "error": repr(e)}
def stated(v):
return {k: v[k] for k in ("outcome", "reason", "at", "statements") if k in v}
def held(doc):
models = {}
for m in doc["models"]:
f = m["file"].encode("utf-8")
models[bytes.fromhex(m["sha256"])] = f
pool = {}
for p in doc["pictures"]:
px = bytes.fromhex(p["rgb8"])
pool[sha256(px)] = px # a picture is named by its samples digest, computed here
return models, pool
def case_of(v, models, pool):
c = dict(v)
c["models_held"] = {bytes.fromhex(x): models[bytes.fromhex(x)] for x in v["models"] if bytes.fromhex(x) in models}
c["pictures"] = {bytes.fromhex(x) for x in v["pictures"]}
return c
def run_vector(v, models, pool):
c = case_of(v, models, pool)
return outcome(c, c["models_held"], pool)
def check(doc):
models, pool = held(doc)
fails = []
for i, m in enumerate(doc.get("measures") or []):
try:
fw, fh = m["frame"]
b = box(m["box"])
if (size_class(b[3], fh), position_class(b, fw, fh)) != (m["size"], m["position"]):
fails.append(f"measure {i}")
except (Bad, KeyError, TypeError, ValueError):
fails.append(f"measure {i}")
for i, t in enumerate(doc.get("interpolations") or []):
try:
got = interpolate(signed(t["from"]), signed(t["to"]), t["n"], t["d"], t["interp"])
if str(got) != t["value"]:
fails.append(f"interpolation {i}")
except (Bad, KeyError, TypeError, ValueError, ZeroDivisionError):
fails.append(f"interpolation {i}")
if not doc.get("measures") or not doc.get("interpolations"):
fails.append("no measures or no interpolations")
for v in doc["vectors"]:
got = run_vector(v, models, pool)
if got != stated(v):
fails.append(v["name"])
return fails
def section(text, head):
at = text.index("\n" + head)
end = text.find("\n## ", at + 1)
return text[at:end if end >= 0 else len(text)]
def reasons():
with open(EXTENSION, encoding="utf-8") as f:
text = f.read()
return re.findall(r"^\| `([a-z_]+)` \|", section(text, "## 13. Reasons"), re.M)
# ---------------------------------------------------------------- mutations
def flip_hex_digit(s, at):
c = s[at]
return s[:at] + ("1" if c == "0" else "0") + s[at + 1:]
def mutations(doc):
"""Copies of the corpus, each with one change a conforming verifier must notice."""
out = []
names = {v["name"]: i for i, v in enumerate(doc["vectors"])}
def mutate(name, about, f):
m = copy.deepcopy(doc)
f(m["vectors"][names[name]], m)
out.append((f"{name}: {about}", m))
def edit_output(f):
def g(v, _):
o = json.loads(v["output"])
f(o)
v["output"] = SV.jcs(o)
return g
mutate("directed", "a box moved in the signed output", edit_output(
lambda o: o["clips"][1]["subjects"][1]["boxes"][0].update(x="47")))
mutate("directed", "a record's signature digit changed", lambda v, _: v["records"].__setitem__(
0, SV.jcs(dict(json.loads(v["records"][0]), sig=flip_hex_digit(json.loads(v["records"][0])["sig"], 5)))))
mutate("directed", "a cut stated a frame late", lambda v, _: v["statements"]["cuts"].__setitem__(0, 5))
mutate("directed", "a frame's director dropped", lambda v, _: v["statements"]["frames"][10]["directed_by"]
.__setitem__("pixel", []))
mutate("directed", "a frame's SHA-256 changed", lambda v, _: v["statements"]["frame_sha256"].__setitem__(
0, flip_hex_digit(v["statements"]["frame_sha256"][0], 0)))
mutate("directed", "a C2PA statement's end frame changed", lambda v, _: v["statements"]["c2pa"][2].__setitem__(
"statement", v["statements"]["c2pa"][2]["statement"].replace('"end":13', '"end":12')))
mutate("directed", "the model file not held", lambda v, _: v.__setitem__("models", []))
mutate("directed", "a byte of the model file changed", lambda v, m: m["models"][0].__setitem__(
"file", m["models"][0]["file"].replace("1", "2", 1)))
def drawing(v, m):
for p in m["pictures"]:
if p["sha256"] in v["pictures"]:
p["rgb8"] = flip_hex_digit(p["rgb8"], 10)
mutate("directed", "a byte of the drawn reference changed", drawing)
mutate("attested", "the attestation's class changed in the statement", lambda v, _: v["statements"]
.__setitem__("class", "detector"))
mutate("attested", "attested stated as exact", lambda v, _: v["statements"].__setitem__("status", "exact"))
mutate("undirected_order", "a sequence element stated where no record sets one", lambda v, _: v["statements"]
["frames"][0]["directed_by"].__setitem__("sequence", [0]))
mutate("cut_a_frame_early", "its refusal stated at another event", lambda v, _: v.__setitem__("at", ["timeline", 2]))
mutate("order_swapped", "its refusal stated with another reason", lambda v, _: v.__setitem__("reason", "cut_mismatch"))
mutate("storyboard_order", "a level dropped from a record's statement", lambda v, _: v["statements"]["records"][0]
.__setitem__("levels", ["shot"]))
def section_mutation(about, f):
m = copy.deepcopy(doc)
f(m)
out.append((about, m))
section_mutation("a measure's size class moved up a class", lambda m: m["measures"][1].__setitem__(
"size", SIZES[[n for n, _ in SIZES].index(m["measures"][1]["size"]) + 1][0]))
section_mutation("an interpolation's value rounded toward zero", lambda m: [
t.__setitem__("value", str(int(t["value"]) + 1)) for t in m["interpolations"]
if t["interp"] == "linear" and t["from"] == "-40" and t["n"] == 1][:1])
section_mutation("the measures left out", lambda m: m.__setitem__("measures", []))
return out
def self_test(doc):
"""Drop each check in turn: the corpus must catch every weakened verifier."""
missed = []
for name in CHECKS:
DROPPED.clear()
DROPPED.add(name)
if not check(doc):
missed.append(name)
DROPPED.clear()
return missed
def main(corpus_dir):
with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f:
doc = json.load(f)
ok = True
if not check_generator():
print("FAIL the generator does not reproduce its conformance vector")
return 1
print("PASS the generator reproduces wai.generate.spectral's conformance vector")
fails = check(doc)
for n in fails:
print("FAIL", n)
print(("PASS" if not fails else "FAIL") + f" every measure, interpolation and vector gets what the corpus states "
f"({len(doc['measures'])} measures, {len(doc['interpolations'])} interpolations, {len(doc['vectors'])} vectors)")
ok &= not fails
codes = reasons()
used = {v.get("reason") for v in doc["vectors"]}
missing = [c for c in codes if c not in used]
print(("PASS" if not missing else "FAIL") + " every reason of §13 refuses a vector" + (f": {missing}" if missing else ""))
ok &= not missing
caught = 0
muts = mutations(doc)
for about, m in muts:
if check(m):
caught += 1
else:
print("FAIL a mutated copy passed:", about)
ok = False
print(("PASS" if caught == len(muts) else "FAIL") + f" every mutated copy fails ({caught} of {len(muts)})")
missed = self_test(doc)
for n in missed:
print("FAIL the corpus does not catch a verifier without its check", n)
print(("PASS" if not missed else "FAIL") + f" the corpus catches the verifier without each of its {len(CHECKS)} checks")
ok &= not missed
return 0 if ok else 1
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: direction_verify.py <live-conformance/direction>")
sys.exit(2)
sys.exit(main(sys.argv[1]))