open-standards/wai/live-conformance/co_watch_verify.py
Transaction Science 2ccc58bd25 co_watch_verify.py: the co-watch corpus's second implementation, written from the extension's text
It verifies each room with live_rooms_verify.py, recomputes the controlled position, samples,
percentiles and verdicts from §5 to §9, requires every mutated copy of the corpus to fail and
catches each of its own checks dropped. Standard library only, run isolated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 03:14:35 -04:00

794 lines
35 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
"""Independent verifier of the co-watch corpus (extensions/co-watch.md).
Written from the extension's text. It shares no code with wai-rs, which generates the corpus,
and never runs it. It checks Ed25519 signatures with ../claim-conformance/_ed25519.py, WAI's
strict rule (SPEC §7 *Signatures*) written from RFC 8032 with the standard library. Each room
is verified by the live-rooms corpus's own second implementation, live_rooms_verify.py
(live-rooms §9), written from its text alone. Everything else is here:
- the co-watch policy (§3): its layout, rules, signature and chain, and the object-sequence
source digest (§3.5);
- the statements (§4): reading, who signs, the control attribute (§4.4);
- controls (§5): who may control, the lead and the horizon, the range, and the order of
application with the overridden ones;
- the controlled position (§6);
- presentation statements (§7);
- windows, due slots, samples, settling, percentiles, verdicts, the skew statement and
completeness (§8);
- the catch-up rule (§9).
For every room it requires the outcome, reason and place, and everything else the corpus
states (§11.3); for every catch-up case, its correction; of the corpus, that every reason of
§12 appears in it and that no other file is there. Then it mutates copies of the corpus, one
defect each, and requires every copy to fail; and it drops each of its own checks in turn and
requires the corpus to catch the weakened verifier.
python3 co_watch_verify.py <live-conformance/cowatch>
python3 co_watch_verify.py --room <room.json>
With `--room`, it verifies one room given in a vector's form (descriptor, grants, chains,
policies, sources) and prints what §11.3 states of it, as JSON: a room a deployment kept,
checked from the text alone.
Exit status 0 only if every case verifies, every mutated copy fails and every weakened
verifier is caught. Python 3.8 or later; standard library only.
"""
import copy
import hashlib
import json
import os
import re
import struct
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 live_events_verify as LEV # noqa: E402
import live_rooms_verify as LRV # noqa: E402
import _ed25519 as ED # noqa: E402
Refuse = LEV.Refuse
# Ed25519 is a pure function of its inputs: each signature is verified once, and every later
# pass over the corpus (its mutated copies, the weakened verifiers) reads the answer back.
_verified = {}
_verify = ED.verify
def _memo_verify(pk, msg, sig, skip=()):
k = (bytes(pk), bytes(msg), bytes(sig), tuple(skip))
if k not in _verified:
_verified[k] = _verify(pk, msg, sig, skip)
return _verified[k]
ED.verify = _memo_verify
EXTENSION = os.path.join(HERE, "..", "extensions", "co-watch.md")
POLICY_MAGIC, CONTROL_MAGIC, PRESENTATION_MAGIC, SKEW_MAGIC = b"WXP1", b"WXC1", b"WXS1", b"WXK1"
POLICY_DOMAIN = b"wai:co-watch-policy\x01" # §3.2
PRESENTATION_DOMAIN = b"wai:co-watch-presentation\x01" # §7.1
SOURCE_DOMAIN = b"wai:co-watch-source\x01" # §3.5
ZERO = bytes(32)
NORMAL = 1000000
META, JOIN, LEAVE, EXCHANGE = 0x01, 0x10, 0x11, 0x14
DIRECT = 2 # live-rooms §3.3, bit 1
PLAY, PAUSE, SEEK, RATE = 1, 2, 3, 4
ACTIONS = {1: "play", 2: "pause", 3: "seek", 4: "rate"}
STATES = {1: "playing", 2: "paused", 3: "stalled", 4: "ended"}
VERDICTS = {1: "in_sync", 2: "out_of_sync", 3: "unknown"}
IN_SYNC, OUT_OF_SYNC, UNKNOWN = 1, 2, 3
U32 = (1 << 32) - 1
# Each check this verifier makes, by name; the self-test drops one at a time (§13).
DROP = set()
def on(name):
return name not in DROP
class Stop(Exception):
def __init__(self, reason, at):
super().__init__(reason)
self.reason, self.at = reason, at
def sha(*parts):
h = hashlib.sha256()
for p in parts:
h.update(p)
return h.digest()
def reasons_of_extension():
"""§12's table: the codes this extension adds, in order."""
with open(EXTENSION, encoding="utf-8") as f:
text = f.read()
sec = text[text.index("\n## 12. Reasons"):]
sec = sec[:sec.index("\n## ", 1)]
return re.findall(r"^\| `([a-z_0-9]+)` \|", sec, re.M)
REASONS = reasons_of_extension()
class Rd:
def __init__(self, b, p=0):
self.b, self.p = b, p
def take(self, n, why):
if self.p + n > len(self.b):
raise Refuse(why)
s = self.b[self.p:self.p + n]
self.p += n
return s
def u8(self, why):
return self.take(1, why)[0]
def u16(self, why):
return struct.unpack("<H", self.take(2, why))[0]
def u32(self, why):
return struct.unpack("<I", self.take(4, why))[0]
def u64(self, why):
return struct.unpack("<Q", self.take(8, why))[0]
# ---------------------------------------------------------------- §3 the policy
def read_policy(b):
"""§3.3: steps 1 to 3, then the rules of §3.1's table."""
if len(b) < 4:
raise Refuse("truncated")
if b[:4] != POLICY_MAGIC:
raise Refuse("malformed")
if len(b) < 5:
raise Refuse("truncated")
if b[4] != 1:
raise Refuse("unknown_version")
r, t = Rd(b, 5), "truncated"
p = {"room": r.take(32, t), "prev": r.take(32, t), "effective": r.u64(t), "source_type": r.u8(t),
"source": r.take(32, t), "duration_us": r.u64(t)}
for k in ("lead_ms", "horizon_ms", "min_rate", "max_rate", "period_ms", "window_ms", "settle_ms", "bound_us"):
p[k] = r.u32(t)
p["quantile"] = r.u8(t)
for k in ("tolerance_us", "nudge", "seek_us"):
p[k] = r.u32(t)
p["body"] = b[:r.p]
p["sig"] = r.take(64, t)
if r.p != len(b):
raise Refuse("malformed")
if on("policy_rules"):
ok = (p["source_type"] in (1, 2, 3) and p["duration_us"] > 0 and p["lead_ms"] > 0 and p["period_ms"] > 0
and p["bound_us"] > 0 and p["nudge"] > 0 and p["horizon_ms"] >= p["lead_ms"]
and 0 < p["min_rate"] <= NORMAL <= p["max_rate"]
and p["window_ms"] % p["period_ms"] == 0 and 1 <= p["window_ms"] // p["period_ms"] <= 1000
and 1 <= p["quantile"] <= 100 and p["tolerance_us"] <= p["bound_us"] and p["seek_us"] > p["bound_us"])
if not ok:
raise Refuse("cw_policy_malformed")
p["digest"] = sha(p["body"])
return p
def source_digest(objects):
"""§3.5: an object sequence's digest."""
out = SOURCE_DOMAIN + struct.pack("<I", len(objects))
for o in objects:
out += bytes.fromhex(o["content_hash"]) + struct.pack("<I", o["duration_ms"])
return sha(out)
def check_policies(d, blobs, sources):
"""§3.3, §3.4, then §3.5 for the sources given: the policies, or Stop at ("policy", i)."""
held = []
for i, b in enumerate(blobs):
try:
p = read_policy(b)
if p["room"] != d["room"]:
raise Refuse("wrong_room")
bad = ED.verify(d["authority"], POLICY_DOMAIN + p["body"], p["sig"])
if bad:
raise Refuse(bad)
if on("policy_link"):
if not held:
linked = p["prev"] == ZERO and p["effective"] == 0
else:
q = held[-1]
same = (p["source_type"], p["source"]) == (q["source_type"], q["source"])
linked = p["prev"] == q["digest"] and p["effective"] > q["effective"] and (not same or p["duration_us"] == q["duration_us"])
if not linked:
raise Refuse("cw_policy_unlinked")
objs = sources[i] if i < len(sources) else None
if objs is not None and on("sources"):
total = sum(o["duration_ms"] for o in objs) * 1000
if p["source_type"] != 2 or source_digest(objs) != p["source"] or total != p["duration_us"]:
raise Refuse("cw_source_mismatch")
except Refuse as e:
raise Stop(e.reason, ["policy", i])
held.append(p)
return held
def policy_at(ps, t):
"""§3.4: the index of the last policy whose effective is at or below t, or None."""
k = None
for i, p in enumerate(ps):
if p["effective"] <= t:
k = i
return k
def starts_of(ps):
"""§3.4, §6.4: (instant, policy index) of each source's start."""
out = []
for i, p in enumerate(ps):
if i == 0 or (p["source_type"], p["source"]) != (ps[i - 1]["source_type"], ps[i - 1]["source"]):
out.append((p["effective"], i))
return out
# ---------------------------------------------------------------- §4 statements
def control_bits(g):
"""§4.4: the actions `watch_control` allows, None without it; Refuse when malformed."""
for k, v in LRV.read_attrs(g["attrs"]) or []:
if k == "watch_control":
if not re.fullmatch(r"[1-9][0-9]?", v) or int(v) > 15:
raise Refuse("cw_attr_malformed")
return int(v)
return None
def may_control(g, action):
"""§5.2."""
if g["powers"] & DIRECT:
return True
try:
bits = control_bits(g)
except Refuse:
return False
return bits is not None and bits >> (action - 1) & 1 == 1
def read_statement(payload):
"""§4.1, §4.2: None for a payload not this extension's; else (kind, fields)."""
if len(payload) < 4 or payload[:4] not in (CONTROL_MAGIC, PRESENTATION_MAGIC, SKEW_MAGIC):
return None
bad = "cw_statement_malformed"
if len(payload) < 5 or payload[4] != 1:
raise Refuse(bad)
r = Rd(payload, 5)
magic = payload[:4]
if magic == CONTROL_MAGIC: # §5.1
s = ("control", {"action": r.u8(bad), "at": r.u64(bad), "arg": r.u64(bad)})
a, arg = s[1]["action"], s[1]["arg"]
if a in (PLAY, PAUSE):
ok = arg == 0
elif a == SEEK:
ok = True
elif a == RATE:
ok = 1 <= arg <= U32
else:
ok = False
if not ok:
raise Refuse(bad)
elif magic == PRESENTATION_MAGIC: # §7.1
f = {"room": r.take(32, bad), "player": r.take(32, bad), "source": r.take(32, bad), "at": r.u64(bad),
"position": r.u64(bad), "state": r.u8(bad), "rate": r.u32(bad)}
f["body"] = payload[:r.p]
f["sig"] = r.take(64, bad)
if f["state"] not in STATES or f["rate"] == 0:
raise Refuse(bad)
s = ("presentation", f)
else: # §8.4
f = {"from": r.u64(bad), "to": r.u64(bad), "bound_us": r.u32(bad), "quantile": r.u8(bad)}
nv = r.u8(bad)
if not 1 <= nv <= 8:
raise Refuse(bad)
f["viewers"] = []
for _ in range(nv):
e = {"principal": r.take(32, bad), "held": r.u64(bad), "due": r.u16(bad), "samples": r.u16(bad),
"counted": r.u16(bad), "p50": r.u32(bad), "pq": r.u32(bad), "max": r.u32(bad), "verdict": r.u8(bad)}
if e["verdict"] not in VERDICTS:
raise Refuse(bad)
f["viewers"].append(e)
keys = [e["principal"] for e in f["viewers"]]
if any(keys[i] >= keys[i + 1] for i in range(len(keys) - 1)):
raise Refuse(bad)
want = [(a, b) for a in range(nv) for b in range(a + 1, nv)]
if r.u8(bad) != len(want):
raise Refuse(bad)
f["pairs"] = []
for a, b in want:
e = {"a": r.u8(bad), "b": r.u8(bad), "n": r.u16(bad), "p50": r.u32(bad), "pq": r.u32(bad),
"max": r.u32(bad), "verdict": r.u8(bad)}
if (e["a"], e["b"]) != (a, b) or e["verdict"] not in VERDICTS:
raise Refuse(bad)
f["pairs"].append(e)
s = ("skew", f)
if r.p != len(payload):
raise Refuse(bad)
return s
# ---------------------------------------------------------------- §5.4, §6 the controlled position
def position(st, x, duration):
"""§6.2."""
if not st["playing"]:
return st["pos"]
return min(duration, st["pos"] + (x - st["anchor"]) * st["rate"] // 1000000)
class Timeline:
"""§5.4 and §6 over the accepted controls (each with its event's t and digest)."""
def __init__(self, policies, controls):
self.policies = policies
self.starts = starts_of(policies)
self.controls = sorted(controls, key=lambda c: (c["at"], c["t"], c["digest"]))
self.overridden = set()
aspect = {PLAY: 0, PAUSE: 0, SEEK: 1, RATE: 2}
for i, c in enumerate(self.controls):
for d in self.controls[i + 1:]:
if d["at"] != c["at"]:
break
if aspect[d["action"]] == aspect[c["action"]]:
self.overridden.add(c["digest"])
events = [(s, 0, k, None) for k, (s, _) in enumerate(self.starts)]
events += [(c["at"], 1, k, c) for k, c in enumerate(self.controls)]
events.sort(key=lambda e: (e[0], e[1], e[2]))
self.points, st = [], None
for x, what, _, c in events:
if what == 0:
st = {"playing": False, "rate": NORMAL, "anchor": x, "pos": 0}
else:
p = position(st, x, self.duration_at(x))
st = dict(st, anchor=x, pos=p)
if c["action"] == PLAY:
st["playing"] = True
elif c["action"] == PAUSE:
st["playing"] = False
elif c["action"] == SEEK:
st["pos"] = c["arg"]
else:
st["rate"] = c["arg"]
self.points.append((x, st))
def duration_at(self, x):
k = policy_at(self.policies, x)
return 0 if k is None else self.policies[k]["duration_us"]
def state_at(self, x):
st = None
for at, s in self.points:
if at <= x:
st = s
return st
def position_at(self, x):
st = self.state_at(x)
return 0 if st is None else position(st, x, self.duration_at(x))
def settle_points(self):
return sorted({c["at"] for c in self.controls} | {s for s, _ in self.starts})
# ---------------------------------------------------------------- §9 catch-up
def catch_up(p, playing, rate, controlled, duration, presented):
"""§9."""
e = presented - controlled
if not playing or controlled >= duration:
return {"action": "seek", "value": controlled} if abs(e) > p["tolerance_us"] else {"action": "hold"}
if abs(e) <= p["tolerance_us"]:
return {"action": "rate", "value": rate}
if abs(e) > p["seek_us"]:
return {"action": "seek", "value": controlled}
r = rate - p["nudge"] if e > 0 else rate + p["nudge"]
return {"action": "rate", "value": max(p["min_rate"], min(p["max_rate"], r))}
# ---------------------------------------------------------------- §8 skew
def window_policy(ps, frm, to):
"""§8.1: the policy of the window frm–to, or None when it is not one."""
if frm >= to:
return None
k = policy_at(ps, frm)
if k is None or policy_at(ps, to - 1) != k:
return None
per = ps[k]["period_ms"] * 1000
n = (to - frm) // per
return ps[k] if (to - frm) % per == 0 and 1 <= n <= 1000 else None
def percentile(vals, q):
"""§8.5: nearest rank; 0 for none."""
if not vals:
return 0
v = sorted(vals)
rank = -(-q * len(v) // 100)
return v[max(rank, 1) - 1]
class Facts:
"""Presence (§8.2), grants, samples (§8.3) and the timeline."""
def __init__(self, timeline, stretches, grant_changes, samples):
self.tl, self.stretches, self.grants, self.samples = timeline, stretches, grant_changes, samples
def subscribes_at(self, w, x):
last = None
for g in self.grants:
if g["principal"] == w and g["t"] <= x:
last = g
return last is not None and last["subscribe"]
def due(self, p, frm, to, w):
per = p["period_ms"] * 1000
out = []
for j in range((to - frm) // per):
s = frm + j * per
inside = any(st["principal"] == w and st["join"] <= s and (st["leave"] is None or st["leave"] >= s + per) for st in self.stretches)
out.append(inside and self.subscribes_at(w, s))
return out
def viewers(self, frm, to):
p = window_policy(self.tl.policies, frm, to)
if p is None:
return []
who = sorted({st["principal"] for st in self.stretches})
return [w for w in who if any(self.due(p, frm, to, w))]
def settling(self, p, w, at):
span = p["settle_ms"] * 1000
if not on("settle"):
return False
near = [x for x in self.tl.settle_points()] + [st["join"] for st in self.stretches if st["principal"] == w]
return any(x <= at < x + span for x in near)
def row(self, p, frm, to, w, held):
due = self.due(p, frm, to, w)
per = p["period_ms"] * 1000
sample = [None] * len(due)
for s in self.samples:
if s["principal"] != w or s["seq"] >= held or not frm <= s["at"] < to:
continue
j = (s["at"] - frm) // per
if not due[j] or sample[j] is not None:
continue
e = s["position"] - self.tl.position_at(s["at"])
sample[j] = (e, not self.settling(p, w, s["at"]))
return due, sample
def entries(self, frm, to, listed):
p = window_policy(self.tl.policies, frm, to)
rows = [self.row(p, frm, to, w, held) for w, held in listed]
bound = p["bound_us"]
def judge(unknown, pq):
return UNKNOWN if unknown else (IN_SYNC if pq <= bound else OUT_OF_SYNC)
viewers = []
for (w, held), (due, sample) in zip(listed, rows):
nd = sum(1 for d in due if d)
ns = sum(1 for s in sample if s is not None)
errs = [abs(s[0]) for s in sample if s is not None and s[1]]
p50, pq = percentile(errs, 50), percentile(errs, p["quantile"])
viewers.append({"principal": w, "held": held, "due": nd, "samples": ns, "counted": len(errs),
"p50": min(p50, U32), "pq": min(pq, U32), "max": min(max(errs, default=0), U32),
"verdict": judge(ns < nd or not errs, pq)})
pairs = []
for a in range(len(listed)):
for b in range(a + 1, len(listed)):
d = [abs(x[0] - y[0]) for x, y in zip(rows[a][1], rows[b][1]) if x is not None and y is not None and x[1] and y[1]]
p50, pq = percentile(d, 50), percentile(d, p["quantile"])
unknown = viewers[a]["verdict"] == UNKNOWN or viewers[b]["verdict"] == UNKNOWN or not d
pairs.append({"a": a, "b": b, "n": len(d), "p50": min(p50, U32), "pq": min(pq, U32),
"max": min(max(d, default=0), U32), "verdict": judge(unknown, pq)})
return viewers, pairs
# ---------------------------------------------------------------- §11 verification
def viewer_json(v):
return {"principal": v["principal"].hex(), "held": v["held"], "due": v["due"], "samples": v["samples"], "counted": v["counted"],
"p50": v["p50"], "pq": v["pq"], "max": v["max"], "verdict": VERDICTS[v["verdict"]]}
def pair_json(p):
return {"a": p["a"], "b": p["b"], "n": p["n"], "p50": p["p50"], "pq": p["pq"], "max": p["max"], "verdict": VERDICTS[p["verdict"]]}
def verify(descriptor, grants, chains, policy_blobs, sources):
"""§11.2: a dict of what the corpus states (§11.3)."""
room = LRV.verify_room(descriptor, grants, None, chains) # step 1
if room["outcome"] == "refuse":
return {"outcome": "refuse", "reason": room["reason"], "at": ["room"] + room["at"]}
gap = room["outcome"] == "gap"
d = LRV.read_descriptor(descriptor)
held_grants, _ = LRV.check_grants(d, grants)
by_digest = {g["digest"]: g for g in held_grants}
room_keys = (d["authority"], d["clock"])
try:
policies = check_policies(d, policy_blobs, sources) # step 2
for i, g in enumerate(held_grants): # step 3
if on("attr"):
try:
control_bits(g)
except Refuse as e:
raise Stop(e.reason, ["grant", i])
records, seen = [], set()
for c, i, under in room["order"]:
ev = LEV.read_record(chains[c][i])
if ev["digest"] in seen:
continue
seen.add(ev["digest"])
ev["chain"], ev["index"], ev["under"] = c, i, bytes.fromhex(under) if under else None
records.append(ev)
stretches, grant_changes = [], [] # §8.2
for ev in records:
if ev["principal"] not in room_keys or ev["kind"] not in (JOIN, LEAVE, EXCHANGE):
continue
pl = LEV.read_payload(ev["kind"], ev["payload"], 1 << 20)
x = LRV.read_detail(ev["kind"], pl["detail"])
who = pl["subject"]
if ev["kind"] == JOIN:
stretches.append({"principal": who, "join": ev["t"], "leave": None})
grant_changes.append({"principal": who, "t": ev["t"], "subscribe": by_digest[x["grant"]]["subscribe"] == 1})
elif ev["kind"] == LEAVE:
for st in reversed(stretches):
if st["principal"] == who and st["leave"] is None:
st["leave"] = ev["t"]
break
else:
grant_changes.append({"principal": who, "t": ev["t"], "subscribe": by_digest[x["new"]]["subscribe"] == 1})
controls, presentations, pending = [], [], []
for ev in records: # step 4
if ev["kind"] != META:
continue
at = ["record", ev["chain"], ev["index"]]
try:
s = read_statement(ev["payload"])
if s is None:
continue
kind, f = s
t = ev["t"]
k = policy_at(policies, t)
if k is None:
raise Refuse("cw_no_policy")
p = policies[k]
is_key = ev["principal"] in room_keys
if kind == "control":
if on("control_power") and not is_key and not may_control(by_digest[ev["under"]], f["action"]):
raise Refuse("control_not_permitted")
if on("lead") and f["at"] < t + p["lead_ms"] * 1000:
raise Refuse("control_not_ahead")
if on("horizon") and f["at"] > t + p["horizon_ms"] * 1000:
raise Refuse("control_beyond_horizon")
q = policies[policy_at(policies, f["at"])]
if on("range") and ((f["action"] == SEEK and f["arg"] > q["duration_us"])
or (f["action"] == RATE and not q["min_rate"] <= f["arg"] <= q["max_rate"])):
raise Refuse("control_out_of_range")
controls.append(dict(f, t=t, digest=ev["digest"], author=ev["principal"]))
elif kind == "presentation":
if on("who_presents") and is_key:
raise Refuse("cw_not_permitted")
if f["room"] != d["room"]:
raise Refuse("wrong_room")
if on("wrong_key") and f["player"] != ev["principal"]:
raise Refuse("presentation_wrong_key")
bad = ED.verify(f["player"], PRESENTATION_DOMAIN + f["body"], f["sig"]) if on("pres_sig") else None
if bad:
raise Refuse(bad)
if on("ahead") and f["at"] > t:
raise Refuse("presentation_ahead")
if on("source") and f["source"] != policies[policy_at(policies, f["at"])]["source"]:
raise Refuse("presentation_wrong_source")
presentations.append(dict(f, digest=ev["digest"], seq=ev["seq"]))
else:
if on("who_skews") and not is_key:
raise Refuse("cw_not_permitted")
wp = window_policy(policies, f["from"], f["to"])
if wp is None or f["to"] > t:
raise Refuse("cw_statement_malformed")
if on("skew_bound") and (f["bound_us"], f["quantile"]) != (wp["bound_us"], wp["quantile"]):
raise Refuse("skew_mismatch")
pending.append((at, ev["digest"], f))
except Refuse as e:
raise Stop(e.reason, at)
tl = Timeline(policies, controls)
facts = Facts(tl, stretches, grant_changes,
[{"principal": p["player"], "seq": p["seq"], "at": p["at"], "position": p["position"]} for p in presentations])
skews = []
counts = {}
for ev in records:
counts[ev["principal"]] = counts.get(ev["principal"], 0) + 1
for at, digest, f in pending: # §8.4 steps 2 and 3
if gap:
skews.append({"digest": digest.hex(), "from": f["from"], "to": f["to"], "viewers": "unchecked", "pairs": "unchecked"})
continue
vs = set(facts.viewers(f["from"], f["to"]))
if on("skew_viewers") and any(v["principal"] not in vs or v["held"] > counts.get(v["principal"], 0) for v in f["viewers"]):
raise Stop("skew_mismatch", at)
viewers, pairs = facts.entries(f["from"], f["to"], [(v["principal"], v["held"]) for v in f["viewers"]])
if on("skew_recompute") and (viewers != f["viewers"] or pairs != f["pairs"]):
raise Stop("skew_mismatch", at)
skews.append({"digest": digest.hex(), "from": f["from"], "to": f["to"],
"viewers": [viewer_json(v) for v in viewers], "pairs": [pair_json(p) for p in pairs]})
if not gap and on("complete"): # step 5, §8.6
windows = {}
for at, _, f in pending:
w = windows.setdefault((f["from"], f["to"]), [at, set()])
w[1].update(v["principal"] for v in f["viewers"])
for (frm, to), (at, listed) in windows.items():
if any(w not in listed for w in facts.viewers(frm, to)):
raise Stop("skew_incomplete", at)
except Stop as s:
return {"outcome": "refuse", "reason": s.reason, "at": s.at}
out = {"outcome": "gap" if gap else "accept", "room": d["room"].hex(), "gaps": room.get("gaps", []),
"policy_digests": [p["digest"].hex() for p in policies],
"starts": [{"at": s, "policy": i} for s, i in tl.starts],
"controls": [{"digest": c["digest"].hex(), "author": c["author"].hex(), "t": c["t"], "action": ACTIONS[c["action"]],
"at": c["at"], "arg": c["arg"], "applied": c["digest"] not in tl.overridden} for c in controls],
"timeline": [{"at": x, "playing": s["playing"], "rate": s["rate"], "position": position(s, x, tl.duration_at(x))} for x, s in tl.points],
"presentations": [], "skews": skews}
for p in presentations:
controlled = tl.position_at(p["at"])
pol = policies[policy_at(policies, p["at"])]
out["presentations"].append({"digest": p["digest"].hex(), "player": p["player"].hex(), "at": p["at"], "position": p["position"],
"state": STATES[p["state"]], "controlled": controlled, "error": p["position"] - controlled,
"settling": facts.settling(pol, p["player"], p["at"])})
return out
def run_vector(v):
sources = []
for s in v["sources"]:
sources.append(None if s is None else s)
return verify(bytes.fromhex(v["descriptor"]), [bytes.fromhex(g) for g in v["grants"]],
[[bytes.fromhex(r) for r in c] for c in v["chains"]], [bytes.fromhex(p) for p in v["policies"]], sources)
STATED = ("outcome", "reason", "at", "room", "gaps", "policy_digests", "starts", "controls", "timeline", "presentations", "skews")
def check(doc, quiet=False, first=False):
"""Every vector and catch-up case of `doc` against this verifier: the failures (with
`first`, the first alone)."""
fails, seen = [], set()
for v in doc["vectors"]:
if first and fails:
return fails, seen
try:
got = run_vector(v)
except Exception as e: # a copy that does not read is a failure to verify it
fails.append("%s: does not verify (%r)" % (v["name"], e))
continue
bad = [k for k in STATED if (k in got or k in v) and got.get(k) != v.get(k)]
if got.get("reason"):
seen.add(got["reason"])
if not quiet:
what = got["outcome"] + ("" if "reason" not in got else " " + got["reason"] + " at %s" % got["at"])
print(("PASS" if not bad else "FAIL") + " room %s: %s" % (v["name"], what))
for k in bad:
fails.append("%s: %s is %s, the corpus states %s" % (v["name"], k, json.dumps(got.get(k))[:200], json.dumps(v.get(k))[:200]))
cu = doc["catch_up"]
for i, c in enumerate(cu["cases"]):
got = catch_up(cu["params"], c["playing"], c["rate"], c["controlled"], c["duration"], c["presented"])
ok = got == c["correction"]
if not quiet:
print(("PASS" if ok else "FAIL") + " catch-up %d: %s -> %s" % (i, c["about"], json.dumps(got)))
if not ok:
fails.append("catch-up case %d: %s, the corpus states %s" % (i, got, c["correction"]))
return fails, seen
def mutations(doc):
"""Copies of the corpus, each with one defect a conforming verifier must catch."""
vn = {v["name"]: k for k, v in enumerate(doc["vectors"])}
party, drift, nxt, gapv = vn["watch_party"], vn["drift"], vn["next_episode"], vn["gap"]
def mutate(label, f):
d = copy.deepcopy(doc)
f(d)
return label, d
def vs(d):
return d["vectors"]
def flip(h, i):
b = bytearray(bytes.fromhex(h))
b[i] ^= 1
return b.hex()
def drop_chain(d, k, principal):
chains = vs(d)[k]["chains"]
vs(d)[k]["chains"] = [c for c in chains if LEV.read_record(bytes.fromhex(c[0]))["principal"].hex() != principal]
late = doc["principals"]["late"]
return [
mutate("a stated 95th percentile", lambda d: vs(d)[party]["skews"][2]["viewers"][0].update(pq=vs(d)[party]["skews"][2]["viewers"][0]["pq"] + 1)),
mutate("a stated verdict", lambda d: vs(d)[drift]["skews"][0]["viewers"][2].update(verdict="in_sync")),
mutate("a stated pair", lambda d: vs(d)[drift]["skews"][2]["pairs"][0].update(n=0)),
mutate("a control stated applied", lambda d: vs(d)[party]["controls"][6].update(applied=True)),
mutate("a stated error", lambda d: vs(d)[party]["presentations"][9].update(error=vs(d)[party]["presentations"][9]["error"] + 1)),
mutate("a statement stated settling", lambda d: vs(d)[party]["presentations"][7].update(settling=not vs(d)[party]["presentations"][7]["settling"])),
mutate("a stated position of the timeline", lambda d: vs(d)[party]["timeline"][3].update(position=1)),
mutate("the second source's start unstated", lambda d: vs(d)[nxt]["starts"].pop()),
mutate("a byte of a presentation statement's own signature", lambda d: vs(d)[party]["chains"][2].__setitem__(4, flip(vs(d)[party]["chains"][2][4], -70))),
mutate("the late joiner's chain withheld", lambda d: drop_chain(d, party, late)),
mutate("an object of the second source one millisecond longer", lambda d: vs(d)[nxt]["sources"][1][0].update(duration_ms=vs(d)[nxt]["sources"][1][0]["duration_ms"] + 1)),
mutate("the gap's skew statements stated checked", lambda d: vs(d)[gapv]["skews"][0].update(viewers=[])),
mutate("a refusal's reason", lambda d: vs(d)[vn["control_short_lead"]].update(reason="control_beyond_horizon")),
mutate("a refusal's place", lambda d: vs(d)[vn["skew_leaves_a_viewer_out"]].update(at=["record", 0, 4])),
mutate("a catch-up correction", lambda d: d["catch_up"]["cases"][3]["correction"].update(value=1040000)),
mutate("the catch-up nudge", lambda d: d["catch_up"]["params"].update(nudge=40000)),
]
CHECKS = ("policy_rules", "policy_link", "sources", "attr", "control_power", "lead", "horizon", "range", "who_presents", "wrong_key",
"pres_sig", "ahead", "source", "who_skews", "skew_bound", "skew_viewers", "skew_recompute", "complete", "settle")
def main(corpus_dir):
fails = []
st = ED._self_test()
print(("PASS" if not st else "FAIL") + " Ed25519 strict rule self-test " + ",".join(st))
fails += ["ed25519 self-test"] if st else []
ok = len(REASONS) == 16
print(("PASS" if ok else "FAIL") + " §12 read from the extension: %d reasons" % len(REASONS))
fails += [] if ok else ["§12 does not read"]
names = sorted(n for n in os.listdir(corpus_dir) if n != "README.md")
ok = names == ["vectors.json"]
print(("PASS" if ok else "FAIL") + " the corpus holds vectors.json and README.md alone")
fails += [] if ok else ["other files: %s" % names]
with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f:
doc = json.load(f)
f2, seen = check(doc)
fails += f2
missing = [r for r in REASONS if r not in seen]
print(("PASS" if not missing else "FAIL") + " every reason of §12 refuses a vector")
fails += ["no vector refused with %s" % ", ".join(missing)] if missing else []
for label, d in mutations(doc):
caught, _ = check(d, quiet=True, first=True)
print(("PASS" if caught else "FAIL") + " mutated copy fails: %s%s" % (label, " (%s)" % caught[0][:90] if caught else ""))
if not caught:
fails.append("a mutated copy verifies: %s" % label)
for name in CHECKS:
DROP.add(name)
try:
caught, _ = check(doc, quiet=True, first=True)
finally:
DROP.discard(name)
print(("PASS" if caught else "FAIL") + " the corpus catches the check dropped: %s%s" % (name, " (%s)" % caught[0][:90] if caught else ""))
if not caught:
fails.append("the corpus does not catch the check dropped: %s" % name)
print("co_watch_verify: %s" % ("PASS" if not fails else "FAIL (%d)" % len(fails)))
for f in fails:
print(" " + f)
return 0 if not fails else 1
def room_main(path):
with open(path, encoding="utf-8") as f:
v = json.load(f)
v.setdefault("sources", [])
print(json.dumps(run_vector(v)))
return 0
if __name__ == "__main__":
if len(sys.argv) == 3 and sys.argv[1] == "--room":
sys.exit(room_main(sys.argv[2]))
if len(sys.argv) != 2:
print("usage: co_watch_verify.py <live-conformance/cowatch> | --room <room.json>", file=sys.stderr)
sys.exit(2)
sys.exit(main(sys.argv[1]))