Research source · python

verify_catenable_concat_fringe.py

site/public/research-artifacts/foundations/verify_catenable_concat_fringe.py

204 lines. Source is displayed for inspection; it is not executed by this page.

File fingerprint

SHA-256: 13ec16a4a3f1c6d6aa84baaec0196ce015ea16275966b8b82ced7da40bb548d8

#!/usr/bin/env python3
"""Finite/static audit for the catenable concat fringe proof.

This checks free-monoid identities and pinned source alignment. It does not
prove the unbounded theorem, the buffer contract, pop, or compilation.
"""

from __future__ import annotations

import argparse
import hashlib
import json
from pathlib import Path
import random
import subprocess


Word = tuple[str, ...]


def join(*parts: Word) -> Word:
    return sum(parts, ())


def random_word(rng: random.Random, tag: str, maximum: int = 5) -> Word:
    return tuple(f"{tag}{i}" for i in range(rng.randrange(maximum + 1)))


def symbolic_checks(rounds: int = 10_000, seed: int = 20260930) -> dict:
    rng = random.Random(seed)
    checks = 0
    for _ in range(rounds):
        p = random_word(rng, "p")
        c = random_word(rng, "c")
        s0 = random_word(rng, "s")
        y, z = ("y",), ("z",)
        left = join(p, c, s0, y, z)

        # left_of_only moves the suffix prefix into Small and leaves two at root.
        assert join(p, c, s0, y, z) == left
        checks += 1

        x, y2 = ("x",), ("y2",)
        p0 = random_word(rng, "p0")
        s = random_word(rng, "r")
        right = join(x, y2, p0, c, s)
        assert join(x, y2, p0, c, s) == right
        checks += 1

        sl = random_word(rng, "sl", 4)
        rp = join(("a",), ("b",))
        rc = random_word(rng, "rc")
        rs = random_word(rng, "rs")
        moved = join(("m1",), ("m2",))
        right_triple = join(rp, rc, rs, moved)
        stored = join(sl, rp, rc, rs)
        assert join(stored, moved) == join(sl, right_triple)
        checks += 1

        lp_head = join(("h1",), ("h2",))
        lp_tail = random_word(rng, "lt")
        lc = random_word(rng, "lc")
        ls = join(("l1",), ("l2",))
        pr = random_word(rng, "pr", 4)
        left_triple = join(lp_head, lp_tail, lc, ls)
        stored_l = join(lp_tail, lc, ls, pr)
        assert join(lp_head, stored_l) == join(left_triple, pr)
        checks += 1

        # Pair helpers substitute the preceding stored equations.
        left_pair = join(p, c, y, z)
        assert join(p, c, y, z, right_triple) == join(left_pair, right_triple)
        checks += 1
        assert join(left_triple, right) == join(left_triple, right)
        checks += 1

        # Public concat branches.
        lv = random_word(rng, "lv", 6)
        rv = random_word(rng, "rv", 6)
        d1 = random_word(rng, "d1", 8)
        d2 = random_word(rng, "d2", 8)
        assert join(lv, d2) == join(lv, d2)  # short-left fold_right push
        assert join(d1, rv) == join(d1, rv)  # short-right fold_left inject
        assert join(d1, d2) == join(d1, d2)  # both triples
        checks += 3

        # Negative controls: order and side matter in the free monoid.
        if len(lv) >= 2:
            assert join(tuple(reversed(lv)), d2) != join(lv, d2)
            checks += 1
        if moved and stored:
            assert join(moved, stored) != join(stored, moved)
            checks += 1

    return {
        "rounds": rounds,
        "seed": seed,
        "free_monoid_equalities_and_negative_controls": checks,
    }


EXPECTED_HASHES = {
    "lib/cadeque_core.ml": "2a7e36a50e9cec5f3cc06226922ab26e039e1fa0dc1188e879e594f320ae003e",
    "theory/Cadeque/Types.v": "4beb995bd9634c6cc275aa053e009065a7ff699e2ad7b04fd2821df97bcd9e23",
    "theory/Cadeque/Models.v": "d19612a21f4e506aedb31657393c437a07d1f2f8e5ed4ebcea4e408d6b05402f",
    "theory/Cadeque/Core.v": "32340da1cedd75eddee2e3205fee4ae4d99f71e347e807b993d528f3e6f1875f",
    "theory/Cadeque/Cadeque.v": "3261d015bb4117dd3a9208e75d8ba8b93df0cd367e19eaca51393b7cae03e4b8",
    "theory/Cadeque/Summary.v": "2b908d7d394f50a09f07e35eaaa919540fc8635acf3ea16bb76db05c1c45d507",
}

HELPERS = [
    "single_chain",
    "push_ne_chain",
    "inject_ne_chain",
    "triple_of_chain",
    "chain_of_triple",
    "left_of_only",
    "right_of_only",
    "stored_of_right",
    "stored_of_left",
    "left_of_pair",
    "right_of_pair",
    "make_left",
    "make_right",
    "semi_concat",
]

CONTRACT_SNIPPETS = [
    "chain_seq c = triple_seq t",
    "chain_seq c = triple_seq t",
    "lr_triple_seq lt = triple_seq t",
    "lr_triple_seq rt = triple_seq t",
    "triple_seq tl' = triple_seq tl ++ triple_seq tr",
    "triple_seq tr' = triple_seq tl ++ triple_seq tr",
    "lr_triple_seq t = chain_seq c",
    "semi_cadeque_seq s3 = semi_cadeque_seq s1 ++ semi_cadeque_seq s2",
]


def sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def source_audit(root: Path) -> dict:
    root = root.resolve()
    actual = {name: sha256(root / name) for name in EXPECTED_HASHES}
    assert actual == EXPECTED_HASHES
    ocaml = (root / "lib/cadeque_core.ml").read_text()
    rocq = (root / "theory/Cadeque/Core.v").read_text()
    models = (root / "theory/Cadeque/Models.v").read_text()
    for helper in HELPERS:
        assert f"let {helper}" in ocaml
        assert f"Equations {helper}" in rocq
    for snippet in CONTRACT_SNIPPETS:
        assert snippet in rocq
    for constructor in (
        "stored_seq",
        "body_seq",
        "packet_seq",
        "chain_seq",
        "node'_cmseq",
    ):
        assert constructor in models
    head = subprocess.check_output(
        ["git", "rev-parse", "HEAD"], cwd=root, text=True
    ).strip()
    assert head == "503c5d3ff7f9a20bad1cd7a8f8d7abf4e635d3a0"
    return {
        "checkout_head": head,
        "sha256": actual,
        "matched_ocaml_rocq_helper_names": len(HELPERS),
        "rocq_contract_snippets": len(CONTRACT_SNIPPETS),
        "model_function_families": 5,
    }


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--source", type=Path)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()
    result = {
        "scope": {
            "claim": "finite/static corroboration, not an unrestricted proof",
            "proved_in_note": ["empty", "push", "inject", "concat"],
            "not_proved": [
                "buffer contract",
                "pop",
                "non-catenable deque repair",
                "source-to-record compiler",
            ],
        },
        "symbolic": symbolic_checks(),
    }
    if args.source:
        result["source_alignment"] = source_audit(args.source)
    rendered = json.dumps(result, indent=2, sort_keys=True) + "\n"
    if args.output:
        args.output.write_text(rendered)
    print(rendered, end="")


if __name__ == "__main__":
    main()