CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/speaker-toolkit

Seven-skill presentation system: ingest talks into a rhetoric vault, run interactive clarification, generate a speaker profile, create presentations that match your documented patterns, produce the deck illustrations + thumbnail visual layer, publish talk pages to a Jekyll shownotes site, and verify a recorded screencast against its storyboard. Includes a 113-entry Presentation Patterns taxonomy (83 observable: 64 patterns + 19 antipatterns; 30 unobservable: 21 patterns + 9 antipatterns) for scoring, brainstorming, and go-live preparation.

74

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

verify-storyboard.pyskills/screencast-recorder/scripts/

#!/usr/bin/env python3
"""Judge a recorded screen sequence against its approved storyboard.

The recording postmortem behind #364 lost a day to a rig that treated "the page
loaded" as proof. #369's answer is that what the viewer can *see* is the
contract, and that a verifier must be able to FAIL each way a take can look
right while being wrong. This is that verifier's manifest lane.

Five verification axes (#369 §5). This script owns the four that read structured
state; the pixel axis needs encoded frames and is reported `unverified`, never
`pass`:

  semantic  route, data fingerprint, required labels, seam state agreement
  geometry  required content in frame, unclipped, labels readable at DELIVERY size
  motion    required pan/scroll performed; pointer on target when the click fires
  time      the proof frame falls inside the ACTUALLY SPOKEN word span
  pixels    NOT CHECKED HERE — reported `unverified`

The distinction between `fail` and `unverified` is the point. A verifier that
reports `pass` for an axis it never examined is worse than no verifier, because
it converts an unknown into a false assurance — which is precisely the failure
#364 paid twenty-four hours for.

Usage:
    verify-storyboard.py <sequence.json>

Stdout: one JSON verdict object. Stderr: actionable diagnostics.
Exit 0 when every checked axis passes, 1 on any failure, 2 on usage error.

Input contract, output shape, and every finding code are documented in
skills/screencast-recorder/references/sequence-contract.md.
"""

from __future__ import annotations

import argparse
import json
import math
import sys
from pathlib import Path
from typing import TypeGuard

# Axis names, fixed so a consumer can group findings without string guessing.
SEMANTIC, GEOMETRY, MOTION, TIME, PIXELS = (
    "semantic",
    "geometry",
    "motion",
    "time",
    "pixels",
)

# State a seam must carry across unchanged. "Same page" is not continuity
# (#369 §2): page, data, framing, tabs, cursor and transient state must agree.
SEAM_FIELDS = (
    "route",
    "data_fingerprint",
    "viewport",
    "zoom",
    "scroll",
    "transform",
    "tabs",
    "active_tab",
)

# Only timings derived from a real transcription can satisfy the time axis.
# A WPM estimate predicts feasibility; it never establishes synchronisation.
ACTUAL_TIMING_SOURCE = "actual_word_timestamps"


def _finding(code, axis, subject, message, **extra):
    """Build a finding. `axis` is the VERIFICATION axis, never a pan/scroll axis.

    A caller describing a spatial axis passes `pan_axis`; `axis` here is one of
    the five verification axes. Because all four fields are named parameters,
    Python rejects a payload that would shadow one with "got multiple values",
    so no explicit guard is needed — an earlier draft added one and it was dead
    code. The first version of this function did collide (`axis="x"` for a pan),
    which is why the distinction is spelled out.
    """
    out = {"code": code, "axis": axis, "subject": subject, "message": message}
    out.update(extra)
    return out


def _rect_contains(outer, inner, margin=0):
    """Is `inner` inside `outer` inset by `margin`? Rects are [x, y, w, h]."""
    ox, oy, ow, oh = outer
    ix, iy, iw, ih = inner
    return (
        ix >= ox + margin
        and iy >= oy + margin
        and ix + iw <= ox + ow - margin
        and iy + ih <= oy + oh - margin
    )


def _point_in_rect(point, rect):
    px, py = point
    rx, ry, rw, rh = rect
    return rx <= px <= rx + rw and ry <= py <= ry + rh


def _phrase_spans(words, phrase):
    """Every [start, end] where `phrase` is spoken, in order. Empty if never.

    All occurrences, not the first: a narration that says "ship it" twice has two
    valid moments to prove against, and matching only the first would fail a take
    whose proof frame is correctly placed during the second.

    Matches on a normalised token sequence so punctuation and spacing from the
    transcriber do not decide whether a phrase was spoken.
    """

    def norm(s):
        return "".join(c for c in s.lower() if c.isalnum())

    target = [norm(w) for w in phrase.split() if norm(w)]
    if not target:
        return []
    toks = [(norm(w.get("word", "")), w) for w in words]
    toks = [(tok, w) for tok, w in toks if tok]
    spans = []
    for i in range(len(toks) - len(target) + 1):
        if [tok for tok, _ in toks[i : i + len(target)]] == target:
            window = [w for _, w in toks[i : i + len(target)]]
            spans.append((float(window[0]["start"]), float(window[-1]["end"])))
    return spans


# What each row requirement needs before it can be judged at all. Without this,
# a requirement whose evidence is missing silently passes — the same "unexamined
# reported as passing" failure the pixel axis is careful to avoid, which this
# script committed everywhere else in its first draft.
EVIDENCE_FOR_REQUIREMENT = {
    # requirement -> (needed field, the axis that requirement is judged on)
    "route": (("entry", "route"), SEMANTIC),
    "data_fingerprint": (("entry", "data_fingerprint"), SEMANTIC),
    "visible_labels": (("entry", "labels"), SEMANTIC),
    # Missing viewport blocks GEOMETRY, not semantic. Filing it under the wrong
    # axis let `axes.geometry` read `pass` while its evidence was absent.
    "content_bounds": (("entry", "viewport"), GEOMETRY),
}


def check_evidence(row, clip, narration, delivery, findings):
    """Refuse to judge a requirement whose evidence the take does not carry.

    Returns the set of requirement names that cannot be judged. Every check
    below skips those: running a predicate whose evidence was just refused
    produces a verdict about nothing, and reports two codes for one defect.
    """
    blocked = set()
    req = row.get("require") or {}
    subject = row["id"]
    labels = (clip.get("entry") or {}).get("labels") or []

    for name, ((section, field), axis) in EVIDENCE_FOR_REQUIREMENT.items():
        if name not in req:
            continue
        if (clip.get(section) or {}).get(field) is None:
            blocked.add(name)
            findings.append(
                _finding(
                    "evidence_missing",
                    axis,
                    subject,
                    f"row requires {name} but the clip carries no {section}.{field}",
                    requirement=name,
                    missing_field=f"{section}.{field}",
                )
            )

    if delivery.get("min_label_px") and req.get("visible_labels"):
        if "scale" not in delivery:
            blocked.add("label_readability")
            findings.append(
                _finding(
                    "evidence_missing",
                    GEOMETRY,
                    subject,
                    "readability is required but delivery.scale is absent, so a "
                    "recorded height cannot be converted to its delivered size",
                    requirement="visible_labels",
                    missing_field="delivery.scale",
                )
            )
        if "visible_labels" in blocked:
            # The labels field is absent entirely, so nothing can be measured.
            # Without this, readability silently reported geometry: pass.
            blocked.add("label_readability")
        present = {label.get("text") for label in labels}
        measured = {label.get("text") for label in labels if "height_px" in label}
        # A label absent from the frame is reported by the semantic axis; asking
        # for its measurement too would be a second code for one root cause.
        unmeasured = [
            x for x in req["visible_labels"] if x in present and x not in measured
        ]
        if unmeasured:
            blocked.add("label_readability")
            findings.append(
                _finding(
                    "evidence_missing",
                    GEOMETRY,
                    subject,
                    "a required label carries no height_px, so readability cannot be judged",
                    requirement="visible_labels",
                    labels=unmeasured,
                )
            )

    if req.get("click_on_target"):
        for click in (e for e in clip.get("events") or [] if e.get("type") == "click"):
            absent = [f for f in ("pointer", "target_rect") if click.get(f) is None]
            if absent:
                blocked.add("click_on_target")
                findings.append(
                    _finding(
                        "evidence_missing",
                        MOTION,
                        subject,
                        "a click event carries no "
                        + " or ".join(absent)
                        + ", so it cannot be judged",
                        requirement="click_on_target",
                        missing_field=", ".join(f"events[].{f}" for f in absent),
                        at_seconds=click.get("t"),
                    )
                )

    if row.get("proof_frame_t") is not None and not (narration.get("words") or []):
        blocked.add("proof_frame_t")
        findings.append(
            _finding(
                "evidence_missing",
                TIME,
                subject,
                "row declares a proof frame but the sequence carries no narration words",
                requirement="proof_frame_t",
                missing_field="narration.words",
            )
        )

    return blocked


def check_semantic(row, clip, findings, exercised, blocked):
    req = row.get("require") or {}
    entry = clip.get("entry") or {}
    subject = row["id"]

    if any(k in req for k in ("route", "data_fingerprint", "visible_labels")):
        exercised.add(SEMANTIC)

    if "route" in req and "route" not in blocked and entry.get("route") != req["route"]:
        findings.append(
            _finding(
                "route_mismatch",
                SEMANTIC,
                subject,
                "clip is not on the route the row requires",
                expected=req["route"],
                actual=entry.get("route"),
            )
        )
    # Negative test 1: correct route, stale data. The route passing is exactly
    # what makes this failure invisible without a data fingerprint.
    if (
        "data_fingerprint" in req
        and "data_fingerprint" not in blocked
        and entry.get("data_fingerprint") != req["data_fingerprint"]
    ):
        findings.append(
            _finding(
                "stale_data",
                SEMANTIC,
                subject,
                "route is correct but the rendered data is not the required revision",
                expected=req["data_fingerprint"],
                actual=entry.get("data_fingerprint"),
            )
        )
    required_labels = (
        [] if "visible_labels" in blocked else req.get("visible_labels") or []
    )
    present = {label.get("text") for label in entry.get("labels") or []}
    missing = [label for label in required_labels if label not in present]
    if missing:
        findings.append(
            _finding(
                "required_label_absent",
                SEMANTIC,
                subject,
                "a label the phrase names is not present in the frame",
                missing=missing,
            )
        )


def check_geometry(row, clip, delivery, findings, exercised, blocked):
    req = row.get("require") or {}
    entry = clip.get("entry") or {}
    subject = row["id"]
    viewport = entry.get("viewport")

    bounds_runs = bool(req.get("content_bounds")) and "content_bounds" not in blocked
    readability_runs = (
        bool(delivery.get("min_label_px"))
        and bool(req.get("visible_labels"))
        and "label_readability" not in blocked
    )
    if bounds_runs or readability_runs:
        exercised.add(GEOMETRY)

    # Negative test 2: the content is present but partly outside the viewport.
    bounds = None if "content_bounds" in blocked else req.get("content_bounds")
    if bounds and viewport:
        margin = req.get("margin_px", 0)
        vp_rect = [0, 0, viewport["width"], viewport["height"]]
        if not _rect_contains(vp_rect, bounds, margin):
            findings.append(
                _finding(
                    "content_clipped",
                    GEOMETRY,
                    subject,
                    "required content is not fully inside the viewport at the declared margin",
                    content_bounds=bounds,
                    viewport=vp_rect,
                    margin_px=margin,
                )
            )

    # Negative test 4: readable in the browser is not readable at delivery size.
    min_px = None if "label_readability" in blocked else delivery.get("min_label_px")
    if min_px:
        scale = delivery.get("scale", 1.0)
        too_small = [
            {
                "text": label.get("text"),
                "delivery_px": round(label["height_px"] * scale, 2),
            }
            for label in entry.get("labels") or []
            if label.get("text") in (req.get("visible_labels") or [])
            and "height_px" in label
            and label["height_px"] * scale < min_px
        ]
        if too_small:
            findings.append(
                _finding(
                    "label_below_readable_size",
                    GEOMETRY,
                    subject,
                    "a required label falls below the readable threshold at delivery resolution",
                    min_label_px=min_px,
                    labels=too_small,
                )
            )


def check_motion(row, clip, findings, exercised, blocked):
    req = row.get("require") or {}
    events = clip.get("events") or []
    subject = row["id"]

    if req.get("pan") or req.get("click_on_target"):
        exercised.add(MOTION)

    # Negative test 3: content wider than the viewport with no pan performed.
    pan_req = req.get("pan")
    if pan_req:
        axis = pan_req.get("axis", "x")
        need = abs(pan_req.get("min_abs_delta", 0))
        moved = max(
            (
                abs(e.get("delta", 0))
                for e in events
                if e.get("type") == "pan" and e.get("axis") == axis
            ),
            default=0,
        )
        if moved < need:
            findings.append(
                _finding(
                    "pan_not_performed",
                    MOTION,
                    subject,
                    "the row requires a deliberate pan that the take does not contain",
                    pan_axis=axis,
                    required_abs_delta=need,
                    observed_abs_delta=moved,
                )
            )

    # Negative test 5: the click fires while the pointer is not on the target.
    if req.get("click_on_target") and "click_on_target" not in blocked:
        clicks = [e for e in events if e.get("type") == "click"]
        if not clicks:
            findings.append(
                _finding(
                    "click_absent",
                    MOTION,
                    subject,
                    "the row requires a visible click and the take contains none",
                )
            )
        for click in clicks:
            pointer, target = click.get("pointer"), click.get("target_rect")
            if pointer and target and not _point_in_rect(pointer, target):
                findings.append(
                    _finding(
                        "cursor_off_target",
                        MOTION,
                        subject,
                        "the pointer was not on the target when the click fired",
                        pointer=pointer,
                        target_rect=target,
                        at_seconds=click.get("t"),
                    )
                )


def check_time(row, narration, findings, exercised, blocked):
    subject = row["id"]
    proof_t = row.get("proof_frame_t")
    phrase = row.get("phrase")
    if proof_t is None or not phrase or "proof_frame_t" in blocked:
        return
    exercised.add(TIME)

    # Negative test 10: timing asserted from predicted WPM. WPM establishes
    # whether a script is deliverable; it never establishes synchronisation.
    if narration.get("source") != ACTUAL_TIMING_SOURCE:
        findings.append(
            _finding(
                "timing_not_from_actual_words",
                TIME,
                subject,
                "synchronisation was asserted without actual transcribed word timestamps",
                source=narration.get("source"),
                required_source=ACTUAL_TIMING_SOURCE,
            )
        )
        return

    spans = _phrase_spans(narration.get("words") or [], phrase)
    if not spans:
        findings.append(
            _finding(
                "phrase_not_spoken",
                TIME,
                subject,
                "the row's phrase does not appear in the transcribed narration",
                phrase=phrase,
            )
        )
        return
    if not any(start <= proof_t <= end for start, end in spans):
        findings.append(
            _finding(
                "proof_outside_phrase",
                TIME,
                subject,
                "the visual proof does not occur while the phrase is being spoken",
                phrase=phrase,
                spoken=[list(s) for s in spans],
                proof_frame_t=proof_t,
            )
        )


def check_seam(previous, following, tolerances, findings, exercised):
    """A seam passes only when the outgoing and incoming state agree (#369 §2)."""
    subject = f"{previous['id']}→{following['id']}"
    exit_state, entry_state = previous.get("exit") or {}, following.get("entry") or {}
    if exit_state and entry_state:
        exercised.add(SEMANTIC)

    for field in SEAM_FIELDS:
        before, after = exit_state.get(field), entry_state.get(field)
        # Absent on both sides compares equal and would pass silently. Two
        # manifests carrying only a route are not a verified continuity join.
        if before is None or after is None:
            findings.append(
                _finding(
                    "evidence_missing",
                    SEMANTIC,
                    subject,
                    f"seam cannot be judged: {field} is missing",
                    field=field,
                    present_on_exit=before is not None,
                    present_on_entry=after is not None,
                )
            )
            continue
        if before == after:
            continue
        # Negative test 7: a numeric transform drifted across the seam.
        tol = tolerances.get(field)
        if tol is not None and isinstance(before, dict) and isinstance(after, dict):
            keys = set(before) | set(after)
            # A key absent from one side is missing evidence; defaulting it to 0
            # would read the gap as "within tolerance".
            comparable = all(
                k in before
                and k in after
                and _is_number(before[k])
                and _is_number(after[k])
                for k in keys
            )
            if comparable and all(abs(before[k] - after[k]) <= tol for k in keys):
                continue
        # Negative test 8: the tab set or active tab changed across the seam.
        code = (
            "seam_tab_mismatch"
            if field in ("tabs", "active_tab")
            else "seam_state_mismatch"
        )
        findings.append(
            _finding(
                code,
                SEMANTIC,
                subject,
                f"seam does not carry {field} across unchanged",
                field=field,
                before=before,
                after=after,
            )
        )


def _is_number(value) -> TypeGuard[float]:
    """A real, finite number. `bool` is an `int` in Python and is not one.

    Finiteness matters: JSON admits NaN, and every comparison against NaN is
    false, so a NaN measurement silently satisfies any threshold it is tested
    against — passing readability while carrying no usable evidence.
    """
    if not isinstance(value, (int, float)) or isinstance(value, bool):
        return False
    try:
        return math.isfinite(value)
    except OverflowError:
        # A Python int is unbounded; 10**400 has no float representation, and
        # math.isfinite raises rather than answering. Unrepresentable is not
        # finite for our purposes, and must not escape as a traceback.
        return False


def _numbers(value, count):
    """True when `value` is a list of exactly `count` real finite numbers."""
    return (
        isinstance(value, list) and len(value) == count and all(map(_is_number, value))
    )


def _rect_problem(where, value):
    """A rect is four finite numbers describing a REGION, or a problem string.

    Zero width or height passes a coordinate-count check and describes nothing:
    a target_rect of [1,1,0,0] contains no point, so no click can land on it,
    yet the containment test reported motion: pass.
    """
    if not _numbers(value, 4):
        return f"{where} must be 4 finite numbers"
    if value[2] <= 0 or value[3] <= 0:
        return f"{where} must have positive width and height"
    return None


def _manifest_problem(where, manifest):
    """Shape problems in one manifest, or None.

    Presence is not enough: `viewport: {}` is present and useless, and accepting
    it let a required `content_bounds` report `geometry: pass` having compared
    nothing. Evidence must be the right SHAPE before it can be judged.
    """
    if manifest is None:
        return None  # absence is handled by the evidence gate, with its axis
    if not isinstance(manifest, dict):
        return f"{where} must be an object"
    if "viewport" in manifest:
        viewport = manifest["viewport"]
        if not isinstance(viewport, dict):
            return f"{where}.viewport must be an object"
        for side in ("width", "height"):
            size = viewport.get(side)
            # _is_number first: `NaN <= 0` is False, so a bare comparison admits NaN.
            if not _is_number(size) or size <= 0:
                return f"{where}.viewport.{side} must be a positive number"
    if "labels" in manifest:
        labels = manifest["labels"]
        if not isinstance(labels, list):
            return f"{where}.labels must be a list"
        for index, label in enumerate(labels):
            if not isinstance(label, dict):
                return f"{where}.labels[{index}] must be an object"
            if not isinstance(label.get("text"), str):
                return f"{where}.labels[{index}].text must be a string"
            if "height_px" in label and not _is_number(label["height_px"]):
                return f"{where}.labels[{index}].height_px must be a finite number"
    if "tabs" in manifest and not isinstance(manifest["tabs"], list):
        return f"{where}.tabs must be a list"
    for name, keys in (
        ("scroll", ("x", "y")),
        ("transform", ("pan_x", "pan_y", "zoom")),
    ):
        block = manifest.get(name)
        if block is None:
            continue
        if not isinstance(block, dict):
            return f"{where}.{name} must be an object"
        # An empty scroll/transform is present but carries no continuity evidence,
        # and two empty ones compare equal — agreement between two absences.
        for key in keys:
            if key not in block:
                return f"{where}.{name} must carry {key}"
            if not _is_number(block[key]):
                return f"{where}.{name}.{key} must be a finite number"
    if "zoom" in manifest and not _is_number(manifest["zoom"]):
        return f"{where}.zoom must be a finite number"
    return None


def structural_problem(sequence):
    """Describe the first contract violation, or None. Runs before verify().

    Covers both shapes verify() would otherwise trip over: a missing key it
    indexes (`clip["id"]`), and evidence present but malformed, which would be
    read as checkable and silently pass.
    """
    for name in ("clips", "rows"):
        items = sequence.get(name, [])
        if not isinstance(items, list):
            return f"{name} must be a list"
        seen = set()
        for index, item in enumerate(items):
            if not isinstance(item, dict):
                return f"{name}[{index}] must be an object"
            if not isinstance(item.get("id"), str) or not item["id"]:
                return f"{name}[{index}] needs a non-empty string id"
            # verify() looks clips up by id; a duplicate silently replaced the
            # earlier one, so a conforming clip could stand in for a failing one.
            if item["id"] in seen:
                return f"{name}[{index}] repeats the id {item['id']!r}"
            seen.add(item["id"])

    for index, clip in enumerate(sequence.get("clips", [])):
        for section in ("entry", "exit"):
            problem = _manifest_problem(f"clips[{index}].{section}", clip.get(section))
            if problem:
                return problem
        if "events" in clip and not isinstance(clip["events"], list):
            return f"clips[{index}].events must be a list"
        events = clip.get("events") or []
        for position, event in enumerate(events or []):
            at = f"clips[{index}].events[{position}]"
            if not isinstance(event, dict):
                return f"{at} must be an object"
            for field in ("t", "delta"):
                if field in event and not _is_number(event[field]):
                    return f"{at}.{field} must be a finite number"
            if event.get("type") == "click":
                if "pointer" in event and not _numbers(event["pointer"], 2):
                    return f"{at}.pointer must be 2 numbers"
                if "target_rect" in event:
                    problem = _rect_problem(f"{at}.target_rect", event["target_rect"])
                    if problem:
                        return problem

    for index, row in enumerate(sequence.get("rows", [])):
        if "require" in row and not isinstance(row["require"], dict):
            return f"rows[{index}].require must be an object"
        require = row.get("require") or {}
        for field in ("route", "data_fingerprint"):
            if field in require and not isinstance(require[field], str):
                return f"rows[{index}].require.{field} must be a string"
        if "visible_labels" in require:
            labels_req = require["visible_labels"]
            if not isinstance(labels_req, list) or not all(
                isinstance(x, str) for x in labels_req
            ):
                return f"rows[{index}].require.visible_labels must be a list of strings"
        if "click_on_target" in require and not isinstance(
            require["click_on_target"], bool
        ):
            return f"rows[{index}].require.click_on_target must be a boolean"
        if "content_bounds" in require:
            # `null` passed validation and then made the geometry check skip
            # itself — a declared requirement that verified nothing.
            problem = _rect_problem(
                f"rows[{index}].require.content_bounds", require["content_bounds"]
            )
            if problem:
                return problem
        if "margin_px" in require:
            margin = require["margin_px"]
            if not _is_number(margin) or margin < 0:
                return f"rows[{index}].require.margin_px must be a non-negative finite number"
        if "pan" in require:
            pan = require["pan"]
            if not isinstance(pan, dict):
                return f"rows[{index}].require.pan must be an object"
            # A pan requirement with no threshold defaulted to 0, which any
            # take satisfies — including one that performed no pan at all.
            if "min_abs_delta" not in pan:
                return f"rows[{index}].require.pan must carry min_abs_delta"
            if not _is_number(pan["min_abs_delta"]) or pan["min_abs_delta"] <= 0:
                return (
                    f"rows[{index}].require.pan.min_abs_delta must be a "
                    "positive finite number"
                )
        if "proof_frame_t" in row:
            proof = row["proof_frame_t"]
            if not _is_number(proof):
                return f"rows[{index}].proof_frame_t must be a finite number"
            # A proof frame with no phrase names no moment to prove against.
            # Skipping it silently let the time axis report pass for the sequence.
            phrase = row.get("phrase")
            if not isinstance(phrase, str) or not phrase.strip():
                return f"rows[{index}] declares proof_frame_t but no phrase to prove it against"

    delivery = sequence.get("delivery") or {}
    if not isinstance(delivery, dict):
        return "delivery must be an object"
    for field in ("width", "height", "min_label_px", "scale"):
        if field not in delivery:
            continue
        value = delivery[field]
        # Non-positive is not merely odd here: min_label_px <= 0 makes the
        # readability predicate vacuously true, and a zero scale erases the
        # measurement it converts.
        if not _is_number(value) or value <= 0:
            return f"delivery.{field} must be a positive finite number"

    tolerances = sequence.get("seam_tolerances") or {}
    if not isinstance(tolerances, dict):
        return "seam_tolerances must be an object"
    for field, value in tolerances.items():
        if not _is_number(value):
            return f"seam_tolerances.{field} must be a finite number"

    narration = sequence.get("narration") or {}
    if not isinstance(narration, dict):
        return "narration must be an object"
    words = narration.get("words", [])
    if not isinstance(words, list):
        return "narration.words must be a list"
    for index, word in enumerate(words):
        if not isinstance(word, dict):
            return f"narration.words[{index}] must be an object"
        for field in ("start", "end"):
            if not _is_number(word.get(field)):
                return f"narration.words[{index}].{field} must be a finite number"
    return None


def verify(sequence):
    findings = []
    exercised = set()
    delivery = sequence.get("delivery") or {}
    narration = sequence.get("narration") or {}
    clips = {c["id"]: c for c in sequence.get("clips", [])}

    if not sequence.get("rows"):
        findings.append(
            _finding(
                "sequence_empty",
                SEMANTIC,
                "sequence",
                "the sequence declares no storyboard rows, so it verifies nothing",
            )
        )

    for row in sequence.get("rows", []):
        clip = clips.get(row.get("clip"))
        if clip is None:
            findings.append(
                _finding(
                    "clip_missing",
                    SEMANTIC,
                    row.get("id", "?"),
                    "the row names a clip the sequence does not contain",
                    clip=row.get("clip"),
                )
            )
            continue
        blocked = check_evidence(row, clip, narration, delivery, findings)
        check_semantic(row, clip, findings, exercised, blocked)
        check_geometry(row, clip, delivery, findings, exercised, blocked)
        check_motion(row, clip, findings, exercised, blocked)
        check_time(row, narration, findings, exercised, blocked)

    ordered = sequence.get("clips", [])
    tolerances = sequence.get("seam_tolerances", {})
    for previous, following in zip(ordered, ordered[1:]):
        check_seam(previous, following, tolerances, findings, exercised)

    failed = {f["axis"] for f in findings}
    axes, unverified = {}, {}
    for axis in (SEMANTIC, GEOMETRY, MOTION, TIME):
        if axis in failed:
            axes[axis] = "fail"
        elif axis in exercised:
            axes[axis] = "pass"
        else:
            # No requirement in this sequence exercised the axis. Reporting pass
            # would claim an assurance nothing was checked to earn.
            axes[axis] = "unverified"
            unverified[axis] = (
                "no row in this sequence declared a requirement on this axis"
            )
    # Never report pass for the axis this script structurally cannot examine.
    axes[PIXELS] = "unverified"
    unverified[PIXELS] = "requires encoded frames; not checked by the manifest lane"

    return {
        "ok": not findings,
        "axes": axes,
        "unverified_axes": unverified,
        "finding_count": len(findings),
        "findings": findings,
        "counts": {
            "rows": len(sequence.get("rows", [])),
            "clips": len(ordered),
            "seams": max(len(ordered) - 1, 0),
        },
    }


def main(argv=None):
    ap = argparse.ArgumentParser(
        description="Verify a recorded screen sequence against its approved storyboard."
    )
    ap.add_argument("sequence", type=Path)
    args = ap.parse_args(argv)

    try:
        sequence = json.loads(args.sequence.read_text(encoding="utf-8"))
    except FileNotFoundError:
        print(
            f"ERROR: sequence file not found: {args.sequence} — emit it from the "
            "recording rig per references/sequence-contract.md.",
            file=sys.stderr,
        )
        return 2
    except json.JSONDecodeError as e:
        print(f"ERROR: {args.sequence} is not valid JSON ({e}).", file=sys.stderr)
        return 2
    except UnicodeDecodeError as e:
        print(
            f"ERROR: {args.sequence} is not UTF-8 text ({e}) — the sequence is a "
            "JSON document, not media.",
            file=sys.stderr,
        )
        return 2
    except OSError as e:
        print(
            f"ERROR: cannot read {args.sequence}: {e.strerror or e} — check the "
            "path and permissions.",
            file=sys.stderr,
        )
        return 2

    if not isinstance(sequence, dict):
        print("ERROR: sequence must be a JSON object.", file=sys.stderr)
        return 2

    problem = structural_problem(sequence)
    if problem:
        print(
            f"ERROR: {args.sequence} does not satisfy the sequence contract: "
            f"{problem} — see references/sequence-contract.md.",
            file=sys.stderr,
        )
        return 2

    try:
        verdict = verify(sequence)
    except (KeyError, TypeError, AttributeError, ValueError, IndexError) as e:
        # structural_problem() should have caught this; if a shape still reaches
        # verify(), the contract promises exit 2 and a diagnostic, not a traceback.
        print(
            f"ERROR: {args.sequence} could not be verified ({type(e).__name__}: {e}) "
            "— see references/sequence-contract.md.",
            file=sys.stderr,
        )
        return 2
    print(json.dumps(verdict, indent=2))
    if not verdict["ok"]:
        print(
            f"{verdict['finding_count']} finding(s); the take does not match its storyboard.",
            file=sys.stderr,
        )
        return 1
    print(
        "manifest lane passed; the pixel axis is UNVERIFIED and still needs frame checks.",
        file=sys.stderr,
    )
    return 0


if __name__ == "__main__":
    sys.exit(main())

skills

README.md

tile.json