Files
ARCADIA-Studio/harness/experiments/default-native-20260913/verify.py
T

173 lines
13 KiB
Python

"""Read-only verification of frozen native receipts and exact saved source bytes."""
from pathlib import Path, PurePosixPath
from datetime import datetime
import hashlib
import io
import json
import zipfile
ROOT = Path(__file__).resolve().parent
HEAD = "df2e1cd65ca5e56f47669e04a9d4bcb426900fcd"
GUI = {"bytes": 115056640, "sha256": "4070927256414a770c5fbcedfaab922810cea33d9f07610b81e9ab8c0465da44"}
CLI = {"bytes": 20608512, "sha256": "d89aba9f10b598926dfdc5c644474ae891b753e8583a184860e941a359ed0eea"}
BUILD = {"bytes": 5538, "sha256": "25c7878f56395ed1e8e6b3532fd9135e0b313fd2b781a4c982f9f2b01a4ec64b"}
INPUTS = {"bytes": 102567, "sha256": "6bf74c1f68431a034681b754617c39d387783f71354c9106649aec9480e4f29f"}
SOURCES = {"bytes": 2483742, "sha256": "e20f33dc363850a9289103a7be28ad613c3fffb2039061957a0186cac72d541b"}
ACTIVITY_PATH = "oa/activities/activity_new_activity.arcadia"
ACTIVITY = b'\noa_activity new_activity "New activity" id "1e1da408-da79-42ab-a990-9d00b7dc2631" {\n}\n\n\n'
FIXTURE = b'''arcadia 7;
project p "Compact source workflow" id "00000000-0000-4000-8000-000000000001" {
oa_activity work "Work" id "00000000-0000-4000-8000-000000000002" {}
diagram view "Overview" id "00000000-0000-4000-8000-000000000003" {}
node placement id "00000000-0000-4000-8000-000000000004" {
diagram @view; element @work;
position { x 120.0; y 80.0; };
port_anchors [] ; // Keep explicit automatic placement.
}
}
'''
LABELS = {
"trial-1": "01-home 02-open-dialog 03-open-file-field 05-open-after-uia-error 06-open-focused 07-open-path-typed 08-project-opened 09-project-settled 10-open-keyboard-ready 11-open-submitted 12-model-explorer 13-created-activity 14-generated-source 15-source-folder 16-activity-source-row 17-compact-new-activity 18-save-before-request 19-save-before-dialog 20-cleanup-save-dialog 21-cleanup-filename-focus 22-cleanup-filename-caret 23-cleanup-save-path 24-cleanup-save-path-settled 25-cleanup-filename-refocus 26-cleanup-save-path-retry 27-cleanup-save-submitted".split(),
"trial-2": "01-home 02-open-baseline 03-baseline-loaded 04-drag-work 05-text-after-move 06-model-actions 07-undo-model 08-redo-menu 09-redo-model 10-save-as-request 11-save-dialog 12-save-filename-focus 13-select-filename 14-enter-save-path 15-correct-path-selection 16-save-path-corrected 17-save-complete 18-home-reopen 19-clear-current-model 20-empty-home 21-reopen-request 22-reopened-root 23-open-source-folder 24-activity-source-list 25-reopened-compact-activity".split(),
}
def require(condition, message):
if not condition:
raise SystemExit(message)
def digest(data):
return {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
def membership(archive, expected, label):
names = archive.namelist()
require(len(names) == len(set(names)) and set(names) == set(expected), label + ": ZIP membership mismatch")
require(all(not PurePosixPath(n).is_absolute() and ".." not in PurePosixPath(n).parts and "\\" not in n and ":" not in n for n in names), label + ": unsafe ZIP member")
def time(value):
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
require(parsed.tzinfo is not None, "Timestamp missing timezone")
return parsed
outer = json.loads((ROOT / "files.json").read_text(encoding="utf-8"))
require(set(outer) == {"README.md", "verify.py", "evidence.zip"}, "Outer manifest membership mismatch")
require({p.name for p in ROOT.iterdir()} == set(outer) | {"files.json"}, "Outer directory membership mismatch")
for name, pin in outer.items():
require(digest((ROOT / name).read_bytes()) == pin, "Outer hash mismatch: " + name)
with zipfile.ZipFile(ROOT / "evidence.zip") as z:
def read(name):
return z.read(name)
def obj(name):
return json.loads(read(name))
pins = obj("capture-files.json")
membership(z, set(pins) | {"capture-files.json"}, "Evidence")
for name, pin in pins.items():
require(digest(read(name)) == pin, "Payload hash mismatch: " + name)
build = obj("build/result.json")
require(digest(read("build/result.json")) == BUILD, "Original build receipt mismatch")
require(build["head"] == HEAD and build["exitCode"] == 0 and build["inputsUnchanged"] is True, "Build closure mismatch")
require(build["inputs"] == INPUTS and build["archive"] == SOURCES, "Build source identity mismatch")
require(all(build.get(k) == v for k, v in obj("build/started.json").items()), "Build start/result mismatch")
require(time(build["start"]) <= time(build["finish"]), "Build chronology mismatch")
require(build["command"] == ["cargo", "build", "--locked", "--offline", "--workspace", "--bins", "--message-format=json"], "Build command mismatch")
for key, name in [("inputs", "inputs.json"), ("archive", "inputs.zip"), ("helper", "gate-policy.py"), ("stdout", "stdout.log"), ("stderr", "stderr.log")]:
require(digest(read("build/" + name)) == build[key], "Build payload mismatch: " + name)
source_pins = obj("build/inputs.json")
require(len(source_pins) == build["inputCount"] == 613, "Source count mismatch")
with zipfile.ZipFile(io.BytesIO(read("build/inputs.zip"))) as sources:
membership(sources, source_pins, "Source")
for name, pin in source_pins.items():
require(digest(sources.read(name)) == pin, "Source blob mismatch: " + name)
build_events = [json.loads(line) for line in read("build/stdout.log").decode().splitlines() if line.startswith("{")]
require([e.get("success") for e in build_events if e.get("reason") == "build-finished"] == [True], "Build success event mismatch")
require([e for e in build_events if e.get("reason") == "compiler-artifact" and e.get("executable")] == [a["cargo"] for a in build["artifacts"]], "Build artifact log mismatch")
binaries = {a["cargo"]["target"]["name"]: a for a in build["artifacts"]}
require(binaries["arcadia-studio"]["readback"] == GUI and binaries["arcadia"]["readback"] == CLI, "Native binary build identity mismatch")
expected = {"build/" + n for n in ["result.json", "started.json", "stdout.log", "stderr.log", "inputs.json", "inputs.zip", "gate-policy.py"]}
expected |= {"native/" + n for n in ["run-native.py", "run-native-followup.py", "capture-snippet.js", "check-fixture.py", "verify-creation-checkpoint.py", "fixture-check.result.json", "fixture-check.stdout.json", "fixture-check.stderr.log", "input/project.arcadia"]}
before = FIXTURE[:-2] + b'\n include "oa/activities/activity_new_activity.arcadia";\n}\n'
after = before.replace(b"position { x 120.0; y 80.0; };", b"position { x 190.0; y 80.0; };")
require(read("native/input/project.arcadia") == FIXTURE, "Input fixture bytes mismatch")
for label, root in [("saved-before", before), ("saved-after", after)]:
expected |= {f"native/{label}/project.arcadia", f"native/{label}/{ACTIVITY_PATH}"}
require(read(f"native/{label}/project.arcadia") == root and read(f"native/{label}/{ACTIVITY_PATH}") == ACTIVITY, label + ": exact source construction mismatch")
previous_finish = time(build["finish"])
for trial, labels in LABELS.items():
prefix = "native/" + trial + "/"
expected |= {prefix + n for n in ["started.json", "result.json", "process.json", "stdout.log", "stderr.log"]}
result, started, process = (obj(prefix + n + ".json") for n in ["result", "started", "process"])
require(all(result.get(k) == v for k, v in started.items()), trial + ": start/result mismatch")
require(result["exitCode"] == 0 and result["binary"] == result["binaryAfter"] == GUI, trial + ": process/binary closure mismatch")
require(result["buildReceipt"] == BUILD and result["buildHead"] == HEAD and result["buildInputs"] == INPUTS, trial + ": build provenance mismatch")
require(result["command"] == [binaries["arcadia-studio"]["cargo"]["executable"]], trial + ": launch command mismatch")
require(previous_finish <= time(result["start"]) <= time(process["observed"]) <= time(result["finish"]), trial + ": chronology mismatch")
require(isinstance(process["pid"], int) and process["pid"] > 0, trial + ": PID mismatch")
for stream in ["stdout", "stderr"]:
require(result[stream] == digest(read(prefix + stream + ".log")) == digest(b""), trial + ": unexpected process output")
previous_capture = time(result["start"])
for label in labels:
expected.add(prefix + label + ".json")
frame = obj(prefix + label + ".json")
require(previous_capture <= time(frame["time"]) <= time(result["finish"]), trial + ": capture chronology mismatch")
previous_capture = time(frame["time"])
require(frame["window"]["id"] and (isinstance(frame["accessibility"], dict) or (trial, label, frame["accessibility"]) == ("trial-1", "08-project-opened", None)), trial + ": capture metadata mismatch")
require(len(frame["images"]) in [1, 2], trial + ": image inventory mismatch")
for i, image in enumerate(frame["images"]):
name = f"{label}-{i}.jpg"
require(image["file"] == name and image["width"] > 0 and image["height"] > 0, trial + ": image metadata mismatch")
require(read(prefix + name).startswith(b"\xff\xd8"), trial + ": JPEG bytes mismatch")
expected.add(prefix + name)
previous_finish = time(result["finish"])
if trial == "trial-2":
expected.add(prefix + "baseline.zip")
require(result["helper"] == digest(read("native/run-native-followup.py")), "Trial 2 producer pin mismatch")
require(result["baselineUnchanged"] is True and result["baselineArchive"] == digest(read(prefix + "baseline.zip")), "Baseline receipt mismatch")
require(result["baseline"] == {"project.arcadia": digest(before), ACTIVITY_PATH: digest(ACTIVITY)}, "Baseline source pins mismatch")
with zipfile.ZipFile(io.BytesIO(read(prefix + "baseline.zip"))) as baseline:
membership(baseline, result["baseline"], "Baseline")
require(baseline.read("project.arcadia") == before and baseline.read(ACTIVITY_PATH) == ACTIVITY, "Launch baseline bytes mismatch")
fixture = obj("native/fixture-check.result.json")
require(fixture["exitCode"] == 0 and fixture["binary"] == CLI, "Fixture CLI receipt mismatch")
require(fixture["fixtureBefore"] == fixture["fixtureAfter"] == digest(FIXTURE), "Fixture CLI source mismatch")
for key, suffix in [("stdout", "stdout.json"), ("stderr", "stderr.log")]:
require(fixture[key] == digest(read("native/fixture-check." + suffix)), "Fixture CLI stream mismatch")
require(read("native/fixture-check.stderr.log") == b"", "Fixture CLI stderr was not empty")
expected.add("native/check-saved-bundles.py")
expected.add("native/bundle-checks/summary.json")
producer = digest(read("native/check-saved-bundles.py"))
require(producer == {"bytes": 4238, "sha256": "b1116c3c5227fad2d99d0d4ab421e0840a09120b1aedfa1fd4364b25787450cc"}, "Saved-bundle producer pin mismatch")
summary = obj("native/bundle-checks/summary.json")
require(summary["status"] == "PASS" and summary["runs"] == ["saved-before", "saved-after"] and summary["producer"] == producer, "Bundle summary mismatch")
diagnostics = [{"severity": "warning", "code": code, "object_id": identity}
for code in ["unallocated_leaf", "work_without_behavior_context"]
for identity in ["00000000-0000-4000-8000-000000000002", "1e1da408-da79-42ab-a990-9d00b7dc2631"]]
for label, source in [("saved-before", before), ("saved-after", after)]:
prefix = "native/bundle-checks/" + label
expected |= {prefix + suffix for suffix in [".result.json", ".stdout.json", ".stderr.log"]}
result, report = obj(prefix + ".result.json"), obj(prefix + ".stdout.json")
entry = "C:\\ar-default-9e8r6t\\.local\\default-omission-native\\" + label + "\\project.arcadia"
require(result["command"] == [binaries["arcadia"]["cargo"]["executable"], "check", entry, "--json"], label + ": CLI command mismatch")
require(result["exitCode"] == 0 and result["binary"] == CLI and result["buildReceipt"] == BUILD and result["producer"] == producer, label + ": CLI provenance mismatch")
require(result["filesUnchanged"] is True and result["files"] == {"project.arcadia": digest(source), ACTIVITY_PATH: digest(ACTIVITY)}, label + ": CLI input mismatch")
require(previous_finish <= time(result["start"]) <= time(result["finish"]), label + ": CLI chronology mismatch")
previous_finish = time(result["finish"])
require(result["stdout"] == digest(read(prefix + ".stdout.json")) and result["stderr"] == digest(read(prefix + ".stderr.log")) == digest(b""), label + ": CLI streams mismatch")
require(report["entry"] == entry and report["operation"] == "check" and report["status"] == "checked" and report["exit_code"] == 0, label + ": CLI status mismatch")
require(report["written"] is False and report["changes"] == [] and report["identities_to_stabilize"] == 0, label + ": CLI changed source or identities")
require(result["diagnostics"] == [{k: d[k] for k in ["severity", "code", "object_id"]} for d in report["diagnostics"]] == diagnostics, label + ": expected draft diagnostics mismatch")
require(set(pins) == expected, "Exact payload inventory mismatch")
print("Verified closed native trials, unchanged verified GUI, original captures, 613 build inputs, and exact saved-bundle bytes.")
print("Intermediate GUI behavior requires screenshot review; no Linux, performance, packaging, or full-release acceptance claim.")