docs: publish Process multiselect verification and native correction evidence
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"""Verify actual rendered feedback bytes against a separate Process checks package."""
|
||||
from pathlib import Path, PurePosixPath
|
||||
from datetime import datetime
|
||||
import argparse
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import stat
|
||||
import zipfile
|
||||
from PIL import Image
|
||||
|
||||
COMMIT = "b15345123a7feaf630ef24d45f591c76c6715778"
|
||||
INPUT_SHA = "7f5b3de4d5bb70282e424fbe90211af2ac259965aef10b05438b46076467a767"
|
||||
TEST = "production_process_multiselect_reverse_click_group_drop_has_one_history_step_and_stable_camera"
|
||||
SOURCES = ("crates/oa-desktop/src/behavior/topology/layout_tests.rs", "crates/oa-desktop/src/behavior/canvas_workspace/tests.rs")
|
||||
NAMES = [f"process-{layer}-{stage}" for layer in ("Oa", "Sa") for stage in ("before-press", "after-motion")]
|
||||
MEMBERS = {"observations.md", "encode-png.py", "capture-1/png-readback.json"} | {f"capture-1/{name}.{ext}" for name in NAMES for ext in ("rgba", "png", "json")}
|
||||
OUTER = {"prepare.py", "verify.py", "README.md", "manifest.json", "evidence.zip"}
|
||||
|
||||
|
||||
def need(ok, message):
|
||||
if not ok:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def pin(data):
|
||||
return {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
|
||||
|
||||
|
||||
def safe(name):
|
||||
return isinstance(name, str) and bool(name) and not PurePosixPath(name).is_absolute() and all(c not in name for c in ("\\", ":")) and all(ord(c) >= 32 for c in name) and all(p not in ("", ".", "..") for p in name.split("/"))
|
||||
|
||||
|
||||
def parsed(data):
|
||||
def unique(items):
|
||||
result = {}
|
||||
for key, value in items:
|
||||
need(key not in result, "duplicate JSON key")
|
||||
result[key] = value
|
||||
return result
|
||||
def constant(value):
|
||||
raise ValueError("nonfinite JSON: " + value)
|
||||
return json.loads(data, object_pairs_hook=unique, parse_constant=constant)
|
||||
|
||||
|
||||
def inventory(archive, expected):
|
||||
names = archive.namelist()
|
||||
need(len(names) == len(set(names)) and set(names) == set(expected), "exact ZIP inventory")
|
||||
for row in archive.infolist():
|
||||
need(safe(row.filename) and not row.is_dir() and not stat.S_ISLNK(row.external_attr >> 16), "unsafe ZIP entry")
|
||||
need(row.file_size == expected[row.filename]["bytes"], "ZIP size")
|
||||
|
||||
|
||||
def checks_data(root):
|
||||
"""Read referenced gate bytes, without importing or executing package code."""
|
||||
files = parsed((root / "files.json").read_bytes())
|
||||
need(set(files) == OUTER, "checks outer inventory")
|
||||
for name, expected in files.items():
|
||||
need(pin((root / name).read_bytes()) == expected, "checks outer pin: " + name)
|
||||
manifest = parsed((root / "manifest.json").read_bytes())
|
||||
need(manifest["schema"] == 1 and manifest["kind"] == "multiselect-cargo-gates", "checks format")
|
||||
with zipfile.ZipFile(root / "evidence.zip") as archive:
|
||||
inventory(archive, manifest["members"])
|
||||
def raw(logical):
|
||||
member = manifest["logicalFiles"][logical]
|
||||
data = archive.read(member)
|
||||
need(pin(data) == manifest["members"][member], "referenced checks pin")
|
||||
return data
|
||||
prefix = "cargo/feedback-1/"
|
||||
gate = {name: raw(prefix + name) for name in ("started.json", "process.json", "result.json", "stdout.log", "stderr.log", "inputs.json", "inputs.zip")}
|
||||
spec = manifest["runs"]["feedback-1"]
|
||||
gate["helper"] = raw(spec["helper"])
|
||||
result, start, process = [parsed(gate[name + ".json"]) for name in ("result", "started", "process")]
|
||||
need(all(result.get(k) == v for k, v in start.items()) and result["command"] == process["command"], "feedback start/process/result")
|
||||
times = [datetime.fromisoformat(t) for t in (start["start"], process["observed"], result["finish"])]
|
||||
need(all(t.utcoffset() is not None for t in times) and times == sorted(times), "closed feedback chronology")
|
||||
need(result["exitCode"] == 0 and result["inputsUnchanged"] is True and TEST in result["command"], "passed exact feedback gate")
|
||||
need(result["environment"]["ARCADIA_RENDER_EVIDENCE_DIR"].replace("\\", "/") == result["cwd"].replace("\\", "/") + "/.local/process-feedback-review/capture-1", "actual output directory provenance")
|
||||
for key, name in (("inputs", "inputs.json"), ("archive", "inputs.zip"), ("stdout", "stdout.log"), ("stderr", "stderr.log"), ("helper", "helper")):
|
||||
need(result[key] == pin(gate[name]), "feedback receipt pin: " + key)
|
||||
need(result["inputs"]["sha256"] == INPUT_SHA, "fixed b153 input map identity")
|
||||
inputs = parsed(gate["inputs.json"])
|
||||
need(len(inputs) == result["inputCount"] == 742 and all(safe(name) for name in inputs), "captured source inventory")
|
||||
with zipfile.ZipFile(io.BytesIO(gate["inputs.zip"])) as capsule:
|
||||
inventory(capsule, inputs)
|
||||
source = {name: capsule.read(name) for name in inputs}
|
||||
need(all(pin(source[name]) == expected for name, expected in inputs.items()), "all captured source bytes")
|
||||
need(("test behavior::topology::view::layout_tests::" + TEST + " ... ok").encode() in gate["stdout.log"], "actual test result")
|
||||
return gate, source
|
||||
|
||||
|
||||
def verify_data(manifest, read, checks):
|
||||
need(manifest["schema"] == 1 and manifest["kind"] == "process-render-feedback" and manifest["sourceCommit"] == COMMIT, "feedback manifest")
|
||||
need(set(manifest["members"]) == MEMBERS, "exact feedback files")
|
||||
for name, expected in manifest["members"].items():
|
||||
need(pin(read(name)) == expected, "feedback pin: " + name)
|
||||
gate, source = checks
|
||||
need(manifest["checksRun"] == "feedback-1" and manifest["checksPins"] == {name: pin(data) for name, data in gate.items()}, "exact checks references")
|
||||
need(manifest["capturingSources"] == {name: pin(source[name]) for name in SOURCES}, "capturing test and renderer source identity")
|
||||
report = parsed(read("capture-1/png-readback.json"))
|
||||
need(set(report["captures"]) == set(NAMES), "four readback records")
|
||||
for layer in ("Oa", "Sa"):
|
||||
previous = None
|
||||
for stage in ("before-press", "after-motion"):
|
||||
name = f"process-{layer}-{stage}"
|
||||
metadata = parsed(read("capture-1/" + name + ".json"))
|
||||
raw = read("capture-1/" + name + ".rgba")
|
||||
need(metadata["layer"] == layer and metadata["stage"] == stage and metadata["format"] == "RGBA8", "capture identity")
|
||||
need(metadata["width"] == 1440 and metadata["height"] == 900 and len(raw) == 1440 * 900 * 4, "exact original dimensions")
|
||||
need(metadata["selectionCount"] == 2 and isinstance(metadata["selection"], str) and "history" not in metadata, "selection metadata; no invented history field")
|
||||
bundle = metadata["source"]
|
||||
need(safe(bundle["entry"]) and bundle["entry"] in bundle["files"] and all(safe(n) and isinstance(s, str) for n, s in bundle["files"].items()), "complete captured source bundle")
|
||||
with Image.open(io.BytesIO(read("capture-1/" + name + ".png"))) as png:
|
||||
need(png.format == "PNG" and png.size == (1440, 900) and png.convert("RGBA").tobytes() == raw, "decoded PNG equals actual raw RGBA")
|
||||
expected = {"bytes": len(raw), "rgbaSha256": pin(raw)["sha256"], "width": 1440, "height": 900}
|
||||
need(report["captures"][name] == expected, "original PNG readback receipt")
|
||||
if previous:
|
||||
for field in ("source", "selection", "selectionCount", "canvas", "camera", "scale", "width", "height"):
|
||||
need(metadata[field] == previous[0][field], layer + " changed " + field + " before release")
|
||||
need(raw != previous[1], "actual before/after pixel difference")
|
||||
previous = metadata, raw
|
||||
return {"captures": 4, "pngPixelsExact": True, "stableMetadataPairs": 2, "checksRun": "feedback-1", "nativeAcceptance": False}
|
||||
|
||||
|
||||
def verify_package(root, checks):
|
||||
files = parsed((root / "files.json").read_bytes())
|
||||
need(set(files) == OUTER, "feedback outer inventory")
|
||||
for name, expected in files.items():
|
||||
need(pin((root / name).read_bytes()) == expected, "feedback outer pin")
|
||||
manifest = parsed((root / "manifest.json").read_bytes())
|
||||
with zipfile.ZipFile(root / "evidence.zip") as archive:
|
||||
inventory(archive, manifest["members"])
|
||||
return verify_data(manifest, archive.read, checks)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("package", nargs="?", type=Path, default=Path(__file__).resolve().parent)
|
||||
parser.add_argument("--checks", type=Path, required=True, help="Process checks package containing closed feedback-1")
|
||||
args = parser.parse_args()
|
||||
print("PASS feedback byte proof:", verify_package(args.package, checks_data(args.checks)))
|
||||
print("History is asserted by the captured test, not recorded in image metadata. No native acceptance claim.")
|
||||
Reference in New Issue
Block a user