#!/usr/bin/env python3
"""Reconcile expected, launch, terminal and public-write receipts.

Expected slots must come from a source outside the run's failure domain.
The tool reports coverage gaps; it cannot prove a missing wake when the
expected-slot source is also absent or corrupted.
"""
from dataclasses import dataclass
from typing import Iterable

@dataclass(frozen=True)
class Row:
    nonce: str
    kind: str
    outcome: str = ""

VALID = {"changed", "confirmed", "no_op", "refused", "failed"}

def reconcile(expected: Iterable[str], launches: Iterable[str], terminals: Iterable[Row], writes: Iterable[str]):
    expected, launches, writes = set(expected), set(launches), set(writes)
    terminals = list(terminals)
    by_nonce = {r.nonce: r for r in terminals}
    errors = []
    for r in terminals:
        if r.outcome not in VALID:
            errors.append(f"invalid outcome:{r.nonce}:{r.outcome}")
    duplicate_terminals = len(terminals) - len(by_nonce)
    if duplicate_terminals:
        errors.append(f"duplicate terminal:{duplicate_terminals}")
    return {
        "missing_launch": sorted(expected - launches),
        "launch_without_expected": sorted(launches - expected),
        "missing_terminal": sorted(launches - by_nonce.keys()),
        "terminal_without_launch": sorted(by_nonce.keys() - launches),
        "write_without_terminal": sorted(writes - by_nonce.keys()),
        "terminal_without_write": sorted(set(by_nonce) - writes),
        "errors": errors,
        "clean": not errors and expected == launches == set(by_nonce) == writes,
    }

def demo():
    clean = reconcile(["a","b"], ["a","b"], [Row("a","terminal","changed"), Row("b","terminal","no_op")], ["a"])
    assert clean["terminal_without_write"] == ["b"]
    assert not clean["clean"]
    missing_launch = reconcile(["a","b"], ["a"], [Row("a","terminal","no_op")], [])
    assert missing_launch["missing_launch"] == ["b"]
    missing_terminal = reconcile(["a"], ["a"], [], [])
    assert missing_terminal["missing_terminal"] == ["a"]
    invalid = reconcile(["a"], ["a"], [Row("a","terminal","bogus")], [])
    assert invalid["errors"] == ["invalid outcome:a:bogus"]
    print("PASS: clean-path and 3 negative controls")
    print(reconcile(["a","b"], ["a","b"], [Row("a","terminal","changed"), Row("b","terminal","no_op")], ["a","b"]))

if __name__ == "__main__":
    demo()
