129 lines
6.6 KiB
Python
129 lines
6.6 KiB
Python
"""Prepare/launch the identified Windows GUI and preserve native trial checkpoints.
|
|
|
|
All actual GUI interaction is performed separately with Computer Use.
|
|
"""
|
|
import argparse
|
|
import datetime
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
|
|
root = Path(__file__).resolve().parents[2]
|
|
evidence = Path(__file__).resolve().parent
|
|
trial = root / ".local/text-navigation-native-20260911"
|
|
fixtures = root / ".local/text-navigation-native-20260911-preparation"
|
|
runtime = root / ".local/toolchain/llvm-mingw-20260826-ucrt-x86_64/bin/libunwind.dll"
|
|
oracle = root / ".local/staged-source-oracle-20260911/staged-source-authoring-contract.exe"
|
|
runtime_sha = "f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4"
|
|
oracle_sha = "967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c"
|
|
fixture_manifest_sha = "edd4157fc31823f4494dee91862728b87f12ef51c49b8d1fd86a954bc9ecc1ed"
|
|
|
|
def sha(path):
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
def now():
|
|
return datetime.datetime.now(datetime.timezone.utc).isoformat()
|
|
|
|
def record(path, value):
|
|
with path.open("x", encoding="utf-8", newline="\n") as out:
|
|
json.dump(value, out, indent=2, ensure_ascii=False)
|
|
out.write("\n")
|
|
|
|
def inventory(folder):
|
|
result = {}
|
|
for path in folder.rglob("*"):
|
|
assert not path.is_symlink() and not (getattr(path.lstat(), "st_file_attributes", 0) & 0x400), path
|
|
if path.is_file():
|
|
result[path.relative_to(folder).as_posix()] = sha(path)
|
|
return result
|
|
|
|
def environment():
|
|
parent = {key.upper(): value for key, value in os.environ.items()}
|
|
names = ("SystemRoot", "SystemDrive", "ProgramData", "ALLUSERSPROFILE", "PUBLIC")
|
|
standard = {name: parent[name.upper()] for name in names}
|
|
assert all("%" not in value for value in standard.values())
|
|
system = standard["SystemRoot"]
|
|
return {**standard, "WINDIR": system, "PATH": system + "\\System32;" + system,
|
|
"USERPROFILE": str(trial / "profile"), "LOCALAPPDATA": str(trial / "profile/local"),
|
|
"APPDATA": str(trial / "profile/roaming"), "TEMP": str(trial / "tmp"), "TMP": str(trial / "tmp")}
|
|
|
|
def verify_fixtures():
|
|
manifest = fixtures / "fixture-manifest.json"
|
|
assert sha(manifest) == fixture_manifest_sha
|
|
for entry in json.loads(manifest.read_text(encoding="utf-8")):
|
|
path = (fixtures / entry["path"]).resolve()
|
|
assert path.is_relative_to(fixtures) and path.stat().st_size == entry["bytes"]
|
|
assert sha(path) == entry["sha256"], entry["path"]
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("action", choices=("launch", "check", "closed"))
|
|
parser.add_argument("--label")
|
|
parser.add_argument("--model", choices=("valid", "release1"), default="valid")
|
|
args = parser.parse_args()
|
|
if args.action == "launch":
|
|
verify_fixtures()
|
|
receipt_path = evidence / "gui-build-v2.json"
|
|
receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
|
|
preserved = json.loads((evidence / "gui-preserved.json").read_text(encoding="utf-8"))
|
|
assert preserved["buildReceiptSha256"] == sha(receipt_path)
|
|
binary = Path(preserved["binary"]).resolve()
|
|
assert binary == evidence / "gui-v2.exe"
|
|
assert preserved["sha256"] == receipt["sha256"]
|
|
assert sha(binary) == receipt["sha256"]
|
|
assert sha(runtime) == runtime_sha and sha(oracle) == oracle_sha
|
|
trial.mkdir(exist_ok=False)
|
|
for folder in ("bin", "profile/local", "profile/roaming", "profile/Desktop", "tmp", "screenshots"):
|
|
(trial / folder).mkdir(parents=True)
|
|
shutil.copy2(binary, trial / "bin/arcadia-studio.exe")
|
|
shutil.copy2(runtime, trial / "bin/libunwind.dll")
|
|
for source, destination in (("valid-baseline", "valid"), ("release1-baseline", "release1"),
|
|
("ambiguous-open", "ambiguous"), ("missing-include-open", "missing-include")):
|
|
shutil.copytree(fixtures / source, trial / destination)
|
|
before = inventory(trial / "bin")
|
|
record(trial / "binary-before.json", before)
|
|
record(trial / "source-before.json", {name: inventory(trial / name) for name in ("valid", "release1", "ambiguous", "missing-include")})
|
|
env = environment()
|
|
startup = subprocess.STARTUPINFO()
|
|
startup.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
startup.wShowWindow = 0
|
|
with (trial / "stdout.log").open("xb") as out, (trial / "stderr.log").open("xb") as err:
|
|
process = subprocess.Popen([str(trial / "bin/arcadia-studio.exe")], cwd=trial / "bin",
|
|
env=env, stdout=out, stderr=err, startupinfo=startup)
|
|
launch = {"atUtc": now(), "pid": process.pid, "binarySha256": receipt["sha256"],
|
|
"buildReceiptSha256": sha(receipt_path), "source": str(trial / "valid/project.arcadia"),
|
|
"environment": env, "scope": "Identified local Windows GUI; native verification pending"}
|
|
record(trial / "launch.json", launch)
|
|
print(json.dumps(launch, ensure_ascii=False))
|
|
elif args.action == "check":
|
|
verify_fixtures()
|
|
assert args.label and args.label.replace("-", "").isalnum()
|
|
assert inventory(trial / "bin") == json.loads((trial / "binary-before.json").read_text(encoding="utf-8"))
|
|
assert sha(oracle) == oracle_sha
|
|
checkpoint = trial / (args.label + "-sources")
|
|
shutil.copytree(trial / args.model, checkpoint)
|
|
env = environment()
|
|
env["PATH"] = str(trial / "bin") + ";" + env["PATH"]
|
|
command = [str(oracle), "same", str(fixtures / (args.model + "-baseline") / "project.arcadia"), str(checkpoint / "project.arcadia")]
|
|
result = subprocess.run(command, capture_output=True, env=env)
|
|
(trial / (args.label + ".stdout.log")).write_bytes(result.stdout)
|
|
(trial / (args.label + ".stderr.log")).write_bytes(result.stderr)
|
|
receipt = {"atUtc": now(), "command": command, "exitCode": result.returncode,
|
|
"oracleSha256": oracle_sha, "files": inventory(checkpoint),
|
|
"binaryFilesUnchanged": True, "scope": "Complete saved Project/identity, reachable sources and physical Arcadia equality"}
|
|
record(trial / (args.label + ".json"), receipt)
|
|
print(result.stdout.decode("utf-8", errors="replace"))
|
|
print(json.dumps({"label": args.label, "exitCode": result.returncode}))
|
|
if result.returncode:
|
|
print(result.stderr.decode("utf-8", errors="replace"))
|
|
raise SystemExit(result.returncode)
|
|
else:
|
|
# The caller must observe process closure independently before this readback.
|
|
before = json.loads((trial / "binary-before.json").read_text(encoding="utf-8"))
|
|
after = inventory(trial / "bin")
|
|
record(trial / "binary-after-close.json", {"atUtc": now(), "files": after, "unchanged": before == after})
|
|
assert before == after
|
|
print(json.dumps({"binaryFilesUnchanged": True, "files": len(after)}))
|