Publish verified Windows authoring prerelease evidence
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Authoring prerelease evidence
|
||||
|
||||
Exact preserved producer, independent reviewer and root native evidence for
|
||||
Windows preview `v0.2.0-preview.authoring.20260911`. `hashes.json` records each
|
||||
copied artifact's original private path, length and SHA256. Git must retain these
|
||||
measured bytes. The executable ZIP and validation notes are release assets.
|
||||
|
||||
The preserved Python files are the original host-specific harnesses. Their
|
||||
relative paths and source/toolchain prerequisites refer to the recorded original
|
||||
`.local` layout; they are not portable standalone tests when run from this archive.
|
||||
To reproduce them, restore that layout in a disposable checkout and use fresh
|
||||
output directories. They never contain Gitea credentials. The GUI launch script
|
||||
only launches the application; native actions were performed using Computer Use.
|
||||
|
||||
See the three `harness/release-authoring-prerelease-*-20260911.md` reports for
|
||||
methods, initial GUI harness failure, passed checks, source provenance and limits.
|
||||
`published-verified.json` records the final release readback; upload records bind
|
||||
the exact downloaded assets. Publication does not complete the wider OA/SA goal.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"pid": 10800,
|
||||
"exe": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\authoring-prerelease-gui-20260911-v2\\extracted\\ArcadiaStudio-0.2.0-preview.authoring.20260911-x86_64-pc-windows-gnullvm\\arcadia-studio.exe",
|
||||
"archive_sha256": "664f30b6217689aca1492eb263b3d15777067d732fa890eeb028f435209dadfa",
|
||||
"files": 559,
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\authoring-prerelease-gui-20260911-v2\\extracted\\ArcadiaStudio-0.2.0-preview.authoring.20260911-x86_64-pc-windows-gnullvm;C:\\Windows\\System32;C:\\Windows",
|
||||
"local_appdata": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\authoring-prerelease-gui-20260911-v2\\profile\\local",
|
||||
"appdata": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\authoring-prerelease-gui-20260911-v2\\profile\\roaming"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import zipfile
|
||||
|
||||
root = Path.cwd()
|
||||
name = 'ArcadiaStudio-0.2.0-preview.authoring.20260911-x86_64-pc-windows-gnullvm'
|
||||
archive = root / '.local/authoring-prerelease-20260911/packages' / (name + '.zip')
|
||||
assert hashlib.sha256(archive.read_bytes()).hexdigest() == '664f30b6217689aca1492eb263b3d15777067d732fa890eeb028f435209dadfa'
|
||||
trial = root / '.local/authoring-prerelease-gui-20260911-v2'
|
||||
trial.mkdir(exist_ok=False)
|
||||
extract = trial / 'extracted'
|
||||
extract.mkdir()
|
||||
with zipfile.ZipFile(archive) as z:
|
||||
names = z.namelist()
|
||||
assert len(names) == len(set(n.casefold() for n in names)) == 559
|
||||
assert all(n.startswith(name + '/') and '..' not in Path(n).parts for n in names)
|
||||
z.extractall(extract)
|
||||
package = extract / name
|
||||
for line in (package / 'SHA256SUMS.txt').read_text().splitlines():
|
||||
digest, path = line.split(' ', 1)
|
||||
assert hashlib.sha256((package / path).read_bytes()).hexdigest() == digest, path
|
||||
for folder in ['profile/local', 'profile/roaming']:
|
||||
(trial / folder).mkdir(parents=True)
|
||||
env = os.environ.copy()
|
||||
env['LOCALAPPDATA'] = str(trial / 'profile/local')
|
||||
env['APPDATA'] = str(trial / 'profile/roaming')
|
||||
env['PATH'] = os.pathsep.join([str(package), str(Path(env['SYSTEMROOT']) / 'System32'), env['SYSTEMROOT']])
|
||||
exe = package / 'arcadia-studio.exe'
|
||||
assert hashlib.sha256(exe.read_bytes()).hexdigest() == 'd4d9c2bd937e301ff1009d7cb93a34ee5aa6dfc837ff7e87d468c85371b20c31'
|
||||
with (trial / 'stdout.log').open('wb') as out, (trial / 'stderr.log').open('wb') as err:
|
||||
process = subprocess.Popen([str(exe)], cwd=package, env=env, stdout=out, stderr=err)
|
||||
result = {'pid': process.pid, 'exe': str(exe), 'archive_sha256': hashlib.sha256(archive.read_bytes()).hexdigest(), 'files': len(names), 'path': env['PATH'], 'local_appdata': env['LOCALAPPDATA'], 'appdata': env['APPDATA']}
|
||||
(trial / 'launch.json').write_text(json.dumps(result, indent=2), encoding='utf-8')
|
||||
print(json.dumps(result, indent=2))
|
||||
@@ -0,0 +1,64 @@
|
||||
[
|
||||
"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\authoring-prerelease-gui-20260911-v2\\extracted\\ArcadiaStudio-0.2.0-preview.authoring.20260911-x86_64-pc-windows-gnullvm\\arcadia-studio.exe",
|
||||
"C:\\Windows\\SYSTEM32\\ntdll.dll",
|
||||
"C:\\Windows\\System32\\KERNEL32.DLL",
|
||||
"C:\\Windows\\System32\\KERNELBASE.dll",
|
||||
"C:\\Windows\\SYSTEM32\\apphelp.dll",
|
||||
"C:\\Windows\\System32\\user32.dll",
|
||||
"C:\\Windows\\System32\\win32u.dll",
|
||||
"C:\\Windows\\System32\\GDI32.dll",
|
||||
"C:\\Windows\\System32\\gdi32full.dll",
|
||||
"C:\\Windows\\System32\\msvcp_win.dll",
|
||||
"C:\\Windows\\System32\\ucrtbase.dll",
|
||||
"C:\\Windows\\System32\\ole32.dll",
|
||||
"C:\\Windows\\System32\\combase.dll",
|
||||
"C:\\Windows\\System32\\RPCRT4.dll",
|
||||
"C:\\Windows\\System32\\advapi32.dll",
|
||||
"C:\\Windows\\System32\\msvcrt.dll",
|
||||
"C:\\Windows\\System32\\sechost.dll",
|
||||
"C:\\Windows\\System32\\bcryptprimitives.dll",
|
||||
"C:\\Windows\\System32\\shell32.dll",
|
||||
"C:\\Windows\\System32\\imm32.dll",
|
||||
"C:\\Windows\\System32\\ws2_32.dll",
|
||||
"C:\\Windows\\System32\\oleaut32.dll",
|
||||
"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\authoring-prerelease-gui-20260911-v2\\extracted\\ArcadiaStudio-0.2.0-preview.authoring.20260911-x86_64-pc-windows-gnullvm\\libunwind.dll",
|
||||
"C:\\Windows\\SYSTEM32\\bcrypt.dll",
|
||||
"C:\\Windows\\SYSTEM32\\dwmapi.dll",
|
||||
"C:\\Windows\\SYSTEM32\\uxtheme.dll",
|
||||
"C:\\Windows\\SYSTEM32\\opengl32.dll",
|
||||
"C:\\Windows\\SYSTEM32\\GLU32.dll",
|
||||
"C:\\Windows\\SYSTEM32\\CRYPTBASE.DLL",
|
||||
"C:\\Windows\\SYSTEM32\\dxcore.dll",
|
||||
"C:\\Windows\\System32\\MSCTF.dll",
|
||||
"C:\\Windows\\SYSTEM32\\kernel.appcore.dll",
|
||||
"C:\\Windows\\System32\\clbcatq.dll",
|
||||
"C:\\Windows\\system32\\dataexchange.dll",
|
||||
"C:\\Windows\\system32\\twinapi.appcore.dll",
|
||||
"C:\\Windows\\system32\\explorerframe.dll",
|
||||
"C:\\Windows\\System32\\SHCORE.dll",
|
||||
"C:\\Windows\\System32\\SHLWAPI.dll",
|
||||
"C:\\Windows\\System32\\Windows.Storage.dll",
|
||||
"C:\\Windows\\SYSTEM32\\powrprof.dll",
|
||||
"C:\\Windows\\SYSTEM32\\UMPDC.dll",
|
||||
"C:\\Windows\\SYSTEM32\\CFGMGR32.dll",
|
||||
"C:\\Windows\\system32\\dxgi.dll",
|
||||
"C:\\Windows\\SYSTEM32\\windows.staterepositorycore.dll",
|
||||
"C:\\Windows\\SYSTEM32\\directxdatabasehelper.dll",
|
||||
"C:\\Windows\\SYSTEM32\\d3d12.dll",
|
||||
"C:\\Windows\\SYSTEM32\\d3dcompiler_47.dll",
|
||||
"C:\\Windows\\SYSTEM32\\cryptsp.dll",
|
||||
"C:\\Windows\\System32\\DriverStore\\FileRepository\\vm3d.inf_amd64_39d3dfd189710365\\vm3dgl64.dll",
|
||||
"C:\\Windows\\System32\\DriverStore\\FileRepository\\vm3d.inf_amd64_39d3dfd189710365\\vm3dglhelper64.dll",
|
||||
"C:\\Windows\\SYSTEM32\\WINMM.dll",
|
||||
"C:\\Windows\\SYSTEM32\\D3D12Core.dll",
|
||||
"C:\\Windows\\SYSTEM32\\d3d10warp.dll",
|
||||
"C:\\Windows\\SYSTEM32\\D3DSCache.dll",
|
||||
"C:\\Windows\\SYSTEM32\\USERENV.dll",
|
||||
"C:\\Windows\\SYSTEM32\\dxilconv.dll",
|
||||
"C:\\Windows\\SYSTEM32\\dcomp.dll",
|
||||
"C:\\Windows\\SYSTEM32\\Microsoft.Internal.WarpPal.dll",
|
||||
"C:\\Windows\\SYSTEM32\\textinputframework.dll",
|
||||
"C:\\Windows\\SYSTEM32\\uiautomationcore.dll",
|
||||
"C:\\Windows\\SYSTEM32\\OLEACC.dll",
|
||||
"C:\\Windows\\System32\\threadpoolwinrt.dll"
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"matched_files": 558,
|
||||
"exact_physical_inventory": 559,
|
||||
"result": "PASS after native packaged walkthrough open and Save"
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
[
|
||||
{
|
||||
"path": "producer-inventory.json",
|
||||
"original": ".local/authoring-prerelease-20260911/inventory.json",
|
||||
"bytes": 66465,
|
||||
"sha256": "3c78dea4e93dae186fd24ab826c6680178a2c7849c270a22deae2ff732f1857b"
|
||||
},
|
||||
{
|
||||
"path": "producer-build-evidence.json",
|
||||
"original": ".local/authoring-prerelease-20260911/build-evidence.json",
|
||||
"bytes": 1230,
|
||||
"sha256": "ccc1ccd087f1de9605ba23a4680e1cb6d37ce5ccf0f8db03c03c4797ae32db25"
|
||||
},
|
||||
{
|
||||
"path": "producer-package-result.json",
|
||||
"original": ".local/authoring-prerelease-20260911/package-initial-result.json",
|
||||
"bytes": 216,
|
||||
"sha256": "45f372960931fa34ca75086df5d346eab9127a7ae70b53aed86751e372c24bae"
|
||||
},
|
||||
{
|
||||
"path": "producer-roundtrip.json",
|
||||
"original": ".local/authoring-prerelease-20260911/committed-roundtrip.json",
|
||||
"bytes": 40443,
|
||||
"sha256": "27e4cd2d6130a9ced76560cee130b5d363478ee0da62925e785691c63fadb098"
|
||||
},
|
||||
{
|
||||
"path": "reviewer-artifact-result.json",
|
||||
"original": ".local/authoring-prerelease-review-20260911/artifact-result.json",
|
||||
"bytes": 338784,
|
||||
"sha256": "8b20e9d294338754ddc9b3320b59f55fc19ab979b8da18819fb47a8036db0bba"
|
||||
},
|
||||
{
|
||||
"path": "reviewer-server-result.json",
|
||||
"original": ".local/authoring-prerelease-review-20260911/server-result.json",
|
||||
"bytes": 128779,
|
||||
"sha256": "086bf21e79f3bc7deddfd3278c8ade9b470b11031f7fb278ca0ead3a75b6cfb1"
|
||||
},
|
||||
{
|
||||
"path": "reviewer.py",
|
||||
"original": ".local/authoring-prerelease-review-20260911/review.py",
|
||||
"bytes": 21434,
|
||||
"sha256": "af9eb20946dfcec78bf8959f1d5dc175e82ebb9fabba9f94cefe590fea516b92"
|
||||
},
|
||||
{
|
||||
"path": "gui-launch.json",
|
||||
"original": ".local/authoring-prerelease-gui-20260911-v2/launch.json",
|
||||
"bytes": 827,
|
||||
"sha256": "ade93cb1abea623f1cc63b8124857cad822e4f75e8d1c389f75fd8a9089b2c39"
|
||||
},
|
||||
{
|
||||
"path": "gui-loaded-modules.json",
|
||||
"original": ".local/authoring-prerelease-gui-20260911-v2/loaded-modules.json",
|
||||
"bytes": 3123,
|
||||
"sha256": "ccae3d91310644e171bf35028f1c0758ef330ebb051983e0524b8cd714652b6b"
|
||||
},
|
||||
{
|
||||
"path": "gui-post-save-exact.json",
|
||||
"original": ".local/authoring-prerelease-gui-20260911-v2/post-save-package-exact.json",
|
||||
"bytes": 133,
|
||||
"sha256": "e340289a2c09058c539e63de33a361326b146fc181a8bd1349a88bfc813f2420"
|
||||
},
|
||||
{
|
||||
"path": "upload-binaries-verified.json",
|
||||
"original": ".local/authoring-prerelease-20260911/upload-binaries-verified.json",
|
||||
"bytes": 685,
|
||||
"sha256": "16799aa759e7287f4c50af1068d311f6f916f97e0cfbd7aaddf7432770d281f3"
|
||||
},
|
||||
{
|
||||
"path": "gui-launch.py",
|
||||
"original": ".local/authoring-prerelease-gui-20260911.py",
|
||||
"bytes": 2001,
|
||||
"sha256": "db256498e44cbb67cd84738a1a37f0e9ab0931d130a3b8c830af2c02cff0924c"
|
||||
},
|
||||
{
|
||||
"path": "home.png",
|
||||
"original": ".local/authoring-prerelease-gui-20260911-v2/home.png",
|
||||
"bytes": 97695,
|
||||
"sha256": "e9c24489e949096347b11234d8d2d343716fd5895903c4978d139107a70e3e5a"
|
||||
},
|
||||
{
|
||||
"path": "oa-walkthrough-save.png",
|
||||
"original": ".local/authoring-prerelease-gui-20260911-v2/oa-walkthrough-save.png",
|
||||
"bytes": 131037,
|
||||
"sha256": "61b2cbdd2dddb6fa176000198ae845554147cbc80abe52fa992211157f6297b5"
|
||||
},
|
||||
{
|
||||
"path": "sa-walkthrough.png",
|
||||
"original": ".local/authoring-prerelease-gui-20260911-v2/sa-walkthrough.png",
|
||||
"bytes": 132180,
|
||||
"sha256": "7f3a2bbed9fa69f9d5d844f9a52f81fbc3928244f49d7a9ff1e475c3f2411dbf"
|
||||
},
|
||||
{
|
||||
"path": "state-workspace.png",
|
||||
"original": ".local/authoring-prerelease-gui-20260911-v2/state-workspace.png",
|
||||
"bytes": 135827,
|
||||
"sha256": "79010527e5e1bb8ccf428788a00dea80e6d8253f3d7c249089218e2df9edd6da"
|
||||
},
|
||||
{
|
||||
"path": "offline-guide.png",
|
||||
"original": ".local/authoring-prerelease-gui-20260911-v2/offline-guide.png",
|
||||
"bytes": 185496,
|
||||
"sha256": "58812d2bd0d6e73daa5187f2dd87564189960387ac4bb1e399af4b8d114c05df"
|
||||
},
|
||||
{
|
||||
"path": "published-verified.json",
|
||||
"original": ".local/authoring-prerelease-20260911/published-verified.json",
|
||||
"bytes": 374,
|
||||
"sha256": "668f28c4d8cf753386085271eb181513b0127940f324f423a2b87614ec8cc85a"
|
||||
},
|
||||
{
|
||||
"path": "upload-validation-verified.json",
|
||||
"original": ".local/authoring-prerelease-20260911/upload-validation-verified.json",
|
||||
"bytes": 277,
|
||||
"sha256": "f39859baea544662fc4bdd1fe14dbcb9bdab5639b173754a34b58b853f0d47a7"
|
||||
}
|
||||
]
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"source_identity": "Exact-byte reconstructed input snapshot 234e01e27d6935d213097b345d91d369f808af82, derived from release base 2ec72d4241fa8971fa6fe9f626937de9dfd7c7eb by seven documented LF-to-CRLF restorations and preservation attributes. All 253 GUI manifest inputs and 120 preserved server source files matched. This derived commit is not claimed as the original build commit; GUI and server original build records are retained separately.",
|
||||
"profile": "Prebuilt Windows debug/development artifacts: GUI cargo build --locked -p oa-desktop --bin arcadia-studio, September 11 2026; preserved server from September 7 workspace bins build. Not a release-profile or reproducible-build claim.",
|
||||
"compiler": "rustc 1.98.1, commit 48a229ceaefd4985c50990b14116b6d856af0985, x86_64-pc-windows-gnullvm, LLVM 22.1.8. Current rustc -vV, all 58 prior-notice target-library hashes and both embedded compiler paths matched; original recorded build evidence retained.",
|
||||
"target": "x86_64-pc-windows-gnullvm",
|
||||
"binary_sha256": {
|
||||
"arcadia-studio.exe": "d4d9c2bd937e301ff1009d7cb93a34ee5aa6dfc837ff7e87d468c85371b20c31",
|
||||
"arcadia-server.exe": "efeeb89c0d2e0f93df4472cb3e9f48b2b722a37ae1043c90a5a56f2e845fe665"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"started": "2026-09-11T12:17:30.7730570Z",
|
||||
"source_commit": "234e01e27d6935d213097b345d91d369f808af82",
|
||||
"success": true,
|
||||
"finished": "2026-09-11T12:21:23.0302800Z",
|
||||
"elapsed_seconds": 232.2812442
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": 1,
|
||||
"tag": "v0.2.0-preview.authoring.20260911",
|
||||
"url": "http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/releases/tag/v0.2.0-preview.authoring.20260911",
|
||||
"draft": false,
|
||||
"prerelease": true,
|
||||
"asset_count": 3,
|
||||
"notes_sha256": "e48399db78b18c6bd779412f7aeccf13fe78bf8c9bac22a4c418755acfab175c",
|
||||
"published_at": "2026-09-11T12:40:51Z"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,335 @@
|
||||
"""Independent frozen archive and packaged-server checks; never runs Cargo or GUI."""
|
||||
from pathlib import Path, PurePosixPath
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import stat
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import time
|
||||
import tomllib
|
||||
import traceback
|
||||
import urllib.request
|
||||
import uuid
|
||||
import zipfile
|
||||
|
||||
OUT = Path(__file__).resolve().parent
|
||||
ROOT = OUT.parents[1]
|
||||
PREP = ROOT / '.local/authoring-prerelease-20260911'
|
||||
NAME = 'ArcadiaStudio-0.2.0-preview.authoring.20260911-x86_64-pc-windows-gnullvm'
|
||||
ZIP = PREP / 'packages' / (NAME + '.zip')
|
||||
EXTRACT = OUT / 'extracted'
|
||||
PACKAGE = EXTRACT / NAME
|
||||
SOURCE = PREP / 'source'
|
||||
SOURCE_COMMIT = '234e01e27d6935d213097b345d91d369f808af82'
|
||||
BASE_COMMIT = '2ec72d4241fa8971fa6fe9f626937de9dfd7c7eb'
|
||||
EXPECTED_ZIP = '664f30b6217689aca1492eb263b3d15777067d732fa890eeb028f435209dadfa'
|
||||
BINARIES = {
|
||||
'arcadia-studio.exe': 'd4d9c2bd937e301ff1009d7cb93a34ee5aa6dfc837ff7e87d468c85371b20c31',
|
||||
'arcadia-server.exe': 'efeeb89c0d2e0f93df4472cb3e9f48b2b722a37ae1043c90a5a56f2e845fe665',
|
||||
'libunwind.dll': 'f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4',
|
||||
}
|
||||
RESULT = {'checks': [], 'scope': sys.argv[1], 'started_utc': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())}
|
||||
|
||||
|
||||
def check(condition, label):
|
||||
if not condition:
|
||||
raise AssertionError(label)
|
||||
RESULT['checks'].append(label)
|
||||
|
||||
|
||||
def sha(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def read_json(path):
|
||||
return json.loads(path.read_text(encoding='utf-8-sig'))
|
||||
|
||||
|
||||
def write_json(path, value):
|
||||
with path.open('x', encoding='utf-8', newline='\n') as f:
|
||||
json.dump(value, f, indent=2, ensure_ascii=False)
|
||||
f.write('\n')
|
||||
|
||||
|
||||
def file_map(directory):
|
||||
result = {}
|
||||
for p in directory.rglob('*'):
|
||||
check(not p.is_symlink() and not (getattr(p.lstat(), 'st_file_attributes', 0) & 0x400), 'ordinary path: ' + p.relative_to(directory).as_posix())
|
||||
if p.is_file():
|
||||
result[p.relative_to(directory).as_posix()] = sha(p.read_bytes())
|
||||
return result
|
||||
|
||||
|
||||
def safe_name(name):
|
||||
p = PurePosixPath(name)
|
||||
return not p.is_absolute() and p.as_posix() == name and '\\' not in name and ':' not in name and all(c not in ('', '.', '..') and not c.endswith(('.', ' ')) and not re.match(r'(?i)^(con|nul|prn|aux|com[1-9]|lpt[1-9])(\.|$)', c) for c in p.parts)
|
||||
|
||||
|
||||
def git_blobs(ref, paths):
|
||||
raw = subprocess.run(['git', 'cat-file', '--batch'], input=''.join(ref + ':' + p + '\n' for p in paths).encode(), cwd=ROOT, capture_output=True, check=True).stdout
|
||||
result, cursor = {}, 0
|
||||
for p in paths:
|
||||
end = raw.index(b'\n', cursor)
|
||||
header = raw[cursor:end].split()
|
||||
check(len(header) == 3 and header[1] == b'blob', 'Git blob exists: ' + ref[:7] + ':' + p)
|
||||
size = int(header[2])
|
||||
result[p] = raw[end + 1:end + 1 + size]
|
||||
cursor = end + 2 + size
|
||||
check(cursor == len(raw), 'Git batch framing complete: ' + ref[:7])
|
||||
return result
|
||||
|
||||
|
||||
def artifact():
|
||||
check(not EXTRACT.exists(), 'clean extraction destination absent')
|
||||
check(ZIP.stat().st_size == 32618262 and sha(ZIP.read_bytes()) == EXPECTED_ZIP, 'frozen ZIP size and SHA256')
|
||||
with zipfile.ZipFile(ZIP) as archive:
|
||||
infos = archive.infolist()
|
||||
names = [i.filename for i in infos]
|
||||
check(len(infos) == 559, '559 ZIP members')
|
||||
check(len({n.casefold() for n in names}) == len(names), 'no duplicate or case-colliding ZIP names')
|
||||
check(all(safe_name(n) and n.startswith(NAME + '/') for n in names), 'one safe ZIP root without traversal or Windows special paths')
|
||||
check(all(not i.is_dir() and stat.S_IFMT(i.external_attr >> 16) in (0, stat.S_IFREG) for i in infos), 'ordinary file-only ZIP entries')
|
||||
check(all(i.date_time == (2000, 1, 1, 0, 0, 0) and i.flag_bits & 1 == 0 for i in infos), 'fixed timestamps and no encrypted entries')
|
||||
check(archive.testzip() is None, 'all ZIP CRCs')
|
||||
EXTRACT.mkdir()
|
||||
archive.extractall(EXTRACT)
|
||||
archive_hashes = {i.filename[len(NAME) + 1:]: sha(archive.read(i)) for i in infos}
|
||||
actual = file_map(PACKAGE)
|
||||
check(actual == archive_hashes, 'complete extracted file set and bytes equal ZIP')
|
||||
sums = {}
|
||||
for line in (PACKAGE / 'SHA256SUMS.txt').read_text().splitlines():
|
||||
match = re.fullmatch(r'([0-9a-f]{64}) (.+)', line)
|
||||
check(match is not None and safe_name(match[2]) and match[2] not in sums, 'well-formed unique checksum row')
|
||||
sums[match[2]] = match[1]
|
||||
check(sums == {p: h for p, h in actual.items() if p != 'SHA256SUMS.txt'}, 'checksum inventory covers every other file exactly')
|
||||
package = read_json(PACKAGE / 'package.json')
|
||||
inputs = package['input_sha256']
|
||||
generated = {'SHA256SUMS.txt', 'START-HERE.txt', 'THIRD-PARTY-NOTICES.txt', 'dependency-notices.json', 'documentation-check.json', 'package.json'}
|
||||
check(set(actual) - set(inputs) == generated and all(actual[p] == h.lower() for p, h in inputs.items()), '553 package input hashes plus exactly six generated files')
|
||||
check(package['version'] == '0.2.0-preview.authoring.20260911' and package['target'] == 'x86_64-pc-windows-gnullvm', 'version and target')
|
||||
check(all(actual[p] == h for p, h in BINARIES.items()), 'exact GUI, server and runtime hashes')
|
||||
for p in BINARIES:
|
||||
data = (PACKAGE / p).read_bytes()
|
||||
offset = struct.unpack_from('<I', data, 0x3c)[0]
|
||||
check(data[:2] == b'MZ' and data[offset:offset + 4] == b'PE\0\0' and struct.unpack_from('<H', data, offset + 4)[0] == 0x8664, 'ordinary PE x86_64: ' + p)
|
||||
check({p for p in actual if '/' not in p} == {'arcadia-server.exe', 'arcadia-studio.exe', 'libunwind.dll', 'LICENSE', 'README.md'} | generated, 'only declared portable top-level files')
|
||||
check({p.split('/')[0] for p in actual if '/' in p} == {'deployment', 'docs', 'examples', 'licenses'}, 'no harness, private profile, source checkout or credential-directory payload')
|
||||
for p in actual:
|
||||
if p.startswith(('docs/', 'examples/', 'deployment/')) or p == 'LICENSE':
|
||||
check((SOURCE / p).is_file() and sha((SOURCE / p).read_bytes()) == actual[p], 'public source document/example bytes: ' + p)
|
||||
RESULT['archive'] = {'path': str(ZIP), 'sha256': EXPECTED_ZIP, 'bytes': ZIP.stat().st_size, 'file_count': len(actual), 'files': actual}
|
||||
RESULT['package_claims'] = {k: package[k] for k in ('version', 'target', 'acceptance', 'build', 'build_evidence')}
|
||||
provenance()
|
||||
notices(actual)
|
||||
|
||||
|
||||
def provenance():
|
||||
gui_path = ROOT / '.local/behavior-creation-20260911/gui-verified/manifest.json'
|
||||
gui = read_json(gui_path)
|
||||
expected = {i['path']: i['sha256'].lower() for i in gui['inputs']}
|
||||
check(len(expected) == 253, 'original GUI manifest has 253 unique inputs')
|
||||
derived = git_blobs(SOURCE_COMMIT, list(expected))
|
||||
base = git_blobs(BASE_COMMIT, list(expected))
|
||||
differences = []
|
||||
for p, h in expected.items():
|
||||
check(sha(derived[p]) == h == sha((SOURCE / p).read_bytes()), 'original GUI input equals derived Git blob and checkout: ' + p)
|
||||
if sha(base[p]) != h:
|
||||
check(sha(base[p].replace(b'\r\n', b'\n').replace(b'\n', b'\r\n')) == h, 'exact LF-to-CRLF-only restoration: ' + p)
|
||||
differences.append(p)
|
||||
check(set(differences) == {'docs/openapi.json'} | {'crates/oa-desktop/src/semantic_review/' + p + '.rs' for p in ('artifacts_view', 'attachments_view', 'keyed_rows', 'keyed_tests', 'keyed', 'ordered')}, 'exact seven documented CRLF restorations')
|
||||
delta = subprocess.run(['git', 'diff', '--name-only', '--ignore-space-at-eol', BASE_COMMIT, SOURCE_COMMIT], cwd=ROOT, check=True, capture_output=True).stdout.decode().splitlines()
|
||||
check(delta == ['.gitattributes'], 'derived commit changes only preservation attributes beyond EOL bytes')
|
||||
origin = read_json(ROOT / 'harness/experiments/textual-authoring-20260911-local/provenance.json')
|
||||
tar_path = ROOT / '.local/linux-qa/runtime/source7-20260907T082753190288Z.tar.gz'
|
||||
check(sha(tar_path.read_bytes()) == 'cbb371a93a562e1e641d7629fe351e016d63e66fc9112499b3fed6d5cd171f78', 'original server source tar identity')
|
||||
server_expected = {i['path']: i['snapshot_sha256'].lower() for i in origin['source_comparisons']}
|
||||
check(len(server_expected) == 120, '120 independently recorded server source files')
|
||||
server_base = git_blobs(BASE_COMMIT, list(server_expected))
|
||||
tar_manifest = read_json(tar_path.with_suffix('').with_suffix('.tar.manifest.json'))
|
||||
with tarfile.open(tar_path) as tar:
|
||||
members = {m.name.removeprefix('./'): m for m in tar.getmembers() if m.isfile()}
|
||||
for p, h in server_expected.items():
|
||||
check(sha(tar.extractfile(members[p]).read()) == h == sha(server_base[p]) == sha((SOURCE / p).read_bytes()), 'server recorded tar equals base Git and staged input: ' + p)
|
||||
shared = [p for p in members if p in ('Cargo.toml', 'Cargo.lock', 'docs/openapi.json') or p.startswith('build/')]
|
||||
for p in shared:
|
||||
check(sha(tar.extractfile(members[p]).read()) == expected[p] == tar_manifest['files'][p].lower(), 'original server/GUI shared manifest or build input: ' + p)
|
||||
evidence = read_json(PACKAGE / 'package.json')['build_evidence']['evidence']
|
||||
check(evidence['binary_sha256'] == {k: v for k, v in BINARIES.items() if k.endswith('.exe')}, 'packaged build evidence binds both exact executables')
|
||||
check(SOURCE_COMMIT in evidence['source_identity'] and BASE_COMMIT in evidence['source_identity'], 'evidence identifies auxiliary and base sources distinctly')
|
||||
check('not claimed as the original build commit' in evidence['source_identity'] and 'Not a release-profile or reproducible-build claim' in evidence['profile'], 'packaged provenance retains explicit limitations')
|
||||
RESULT['provenance'] = {'gui_manifest_sha256': sha(gui_path.read_bytes()), 'gui_build_record': gui['build'], 'historical_gui_head': gui['head'], 'gui_inputs': 253, 'source_commit': SOURCE_COMMIT, 'base_commit': BASE_COMMIT, 'eol_restorations': differences, 'server_source_files': 120, 'shared_input_files': shared, 'tar_sha256': sha(tar_path.read_bytes())}
|
||||
|
||||
|
||||
def notices(actual):
|
||||
notice = read_json(PACKAGE / 'dependency-notices.json')
|
||||
check(notice['root_packages'] == ['oa-desktop', 'oa-server'] and len(notice['packages']) == 266, '266 notice packages and exact desktop/server roots')
|
||||
check(notice['cargo_lock_sha256'].lower() == sha((SOURCE / 'Cargo.lock').read_bytes()), 'notice lock equals exact historical source lock')
|
||||
for p, h in notice['workspace_build_inputs_sha256'].items():
|
||||
check(sha((SOURCE / p).read_bytes()) == h, 'notice common build input: ' + p)
|
||||
lock = tomllib.loads((SOURCE / 'Cargo.lock').read_text())
|
||||
locked = {(p['name'], p['version'], p.get('source')): p for p in lock['package']}
|
||||
referenced = set()
|
||||
def refs(items):
|
||||
for i in items:
|
||||
p = i['path']
|
||||
check(safe_name(p) and p.startswith('licenses/') and actual.get(p) == i['sha256'].lower(), 'bundled exact notice reference: ' + p)
|
||||
referenced.add(p)
|
||||
fonts = []
|
||||
for p in notice['packages']:
|
||||
key = (p['name'], p['version'], p['source'])
|
||||
check(key in locked and p['registry_checksum'] == locked[key].get('checksum'), 'notice identity/checksum occurs in historical lock: ' + p['name'] + '@' + p['version'])
|
||||
check(len(p['notices']) > 0, 'package has notice text: ' + p['name'])
|
||||
refs(p['notices'])
|
||||
for f in p['embedded_fonts']:
|
||||
check(len(f['notices']) > 0, 'embedded font notice present: ' + p['name'] + '/' + f['path'])
|
||||
refs(f['notices'])
|
||||
fonts.append({'package': p['name'], 'path': f['path'], 'sha256': f['sha256'], 'license': f['license']})
|
||||
if p['source'] is None:
|
||||
directory = SOURCE / 'crates' / p['name']
|
||||
files = {f.relative_to(directory).as_posix(): sha(f.read_bytes()) for f in directory.rglob('*') if f.is_file() and 'target' not in f.relative_to(directory).parts and f.name != '.cargo-ok'}
|
||||
check(files == p['local_source_sha256'], 'complete ordinary local-crate inventory: ' + p['name'])
|
||||
refs(notice['rust']['notices'])
|
||||
refs(notice['runtime']['notices'])
|
||||
check(notice['runtime']['sha256'] == BINARIES['libunwind.dll'], 'runtime notice is bound to shipped DLL')
|
||||
library_dir = ROOT / '.local/toolchain/rustup/toolchains/stable-x86_64-pc-windows-gnullvm/lib/rustlib/x86_64-pc-windows-gnullvm/lib'
|
||||
check(len(notice['rust']['target_libraries_sha256']) == 58, '58 compiler target libraries recorded')
|
||||
for name, h in notice['rust']['target_libraries_sha256'].items():
|
||||
check(sha((library_dir / name).read_bytes()) == h, 'retained compiler target-library bytes: ' + name)
|
||||
RESULT['notices'] = {'packages': len(notice['packages']), 'roots': notice['root_packages'], 'lock_sha256': notice['cargo_lock_sha256'], 'notice_reference_files': len(referenced), 'additional_license_files': sorted({p for p in actual if p.startswith('licenses/')} - referenced), 'embedded_fonts': fonts, 'rust_version': notice['rust']['version'], 'runtime_provenance': notice['runtime']['provenance'], 'scope': notice['scope']}
|
||||
|
||||
|
||||
def server():
|
||||
prior = read_json(OUT / 'artifact-result.json')
|
||||
check(prior['status'] == 'PASS' and file_map(PACKAGE) == prior['archive']['files'], 'previously verified extracted package is unchanged')
|
||||
runtime = OUT / 'server-run'
|
||||
runtime.mkdir()
|
||||
data_dir = runtime / 'data'
|
||||
temp = runtime / 'temp'
|
||||
temp.mkdir()
|
||||
windows = Path(os.environ['SystemRoot'])
|
||||
env = {'SystemRoot': str(windows), 'WINDIR': str(windows), 'PATH': str(windows / 'System32') + ';' + str(windows), 'TEMP': str(temp), 'TMP': str(temp)}
|
||||
exe = PACKAGE / 'arcadia-server.exe'
|
||||
RESULT['environment'] = env
|
||||
RESULT['processes'] = []
|
||||
for arg in ('--version', '--help'):
|
||||
output = subprocess.run([str(exe), arg], cwd=PACKAGE, env=env, capture_output=True, timeout=15, creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
(runtime / (arg[2:] + '.stdout')).write_bytes(output.stdout)
|
||||
(runtime / (arg[2:] + '.stderr')).write_bytes(output.stderr)
|
||||
check(output.returncode == 0 and not output.stderr, arg + ' succeeds under clean runtime PATH')
|
||||
if arg == '--version':
|
||||
check(output.stdout.decode().strip() == 'arcadia-server 0.2.0', 'server reports underlying application version 0.2.0')
|
||||
check(not data_dir.exists(), 'help/version do not create explicit test data directory')
|
||||
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
request_number = 0
|
||||
def request(port, method, path, payload=None):
|
||||
nonlocal request_number
|
||||
request_number += 1
|
||||
body = None if payload is None else json.dumps(payload, ensure_ascii=False).encode()
|
||||
req = urllib.request.Request(f'http://127.0.0.1:{port}' + path, data=body, method=method, headers={} if body is None else {'Content-Type': 'application/json'})
|
||||
with opener.open(req, timeout=10) as response:
|
||||
raw = response.read()
|
||||
meta = {'method': method, 'path': path, 'status': response.status, 'headers': dict(response.headers), 'body_sha256': sha(raw)}
|
||||
prefix = runtime / f'http-{request_number:02d}'
|
||||
prefix.with_suffix('.body').write_bytes(raw)
|
||||
write_json(prefix.with_suffix('.json'), meta)
|
||||
return meta, raw
|
||||
def start(index):
|
||||
with socket.socket() as s:
|
||||
s.bind(('127.0.0.1', 0))
|
||||
port = s.getsockname()[1]
|
||||
out = (runtime / f'server-{index}.stdout').open('xb')
|
||||
err = (runtime / f'server-{index}.stderr').open('xb')
|
||||
args = [str(exe), '--bind', f'127.0.0.1:{port}', '--data-dir', str(data_dir)]
|
||||
process = subprocess.Popen(args, cwd=PACKAGE, env=env, stdout=out, stderr=err, creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
out.close()
|
||||
err.close()
|
||||
RESULT['processes'].append({'pid': process.pid, 'port': port, 'arguments': args})
|
||||
deadline = time.monotonic() + 15
|
||||
while True:
|
||||
if process.poll() is not None:
|
||||
raise AssertionError('packaged server exited before listening')
|
||||
try:
|
||||
with socket.create_connection(('127.0.0.1', port), timeout=.2):
|
||||
break
|
||||
except OSError:
|
||||
if time.monotonic() > deadline:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
raise AssertionError('packaged server did not listen within 15s')
|
||||
time.sleep(.05)
|
||||
return process, port
|
||||
def stop(process):
|
||||
process.terminate()
|
||||
code = process.wait(timeout=10)
|
||||
RESULT['processes'][-1]['exit_after_explicit_terminate'] = code
|
||||
process = None
|
||||
try:
|
||||
process, port = start(1)
|
||||
for path in ('/health', '/health/ready'):
|
||||
meta, raw = request(port, 'GET', path)
|
||||
check(meta['status'] == 200 and json.loads(raw) == {'status': 'ok'}, path + ' contract')
|
||||
meta, raw = request(port, 'GET', '/api/v1/openapi.json')
|
||||
check(meta['status'] == 200 and raw == (PACKAGE / 'docs/openapi.json').read_bytes(), 'served OpenAPI equals complete packaged source bytes')
|
||||
name = 'Independent package persistence — 救助 20260911'
|
||||
meta, raw = request(port, 'POST', '/api/v1/projects', {'name': name})
|
||||
created = json.loads(raw)
|
||||
check(meta['status'] == 201, 'one create returns 201')
|
||||
resource = str(uuid.UUID(created['resource_id']))
|
||||
model_id = str(uuid.UUID(created['model_id']))
|
||||
expected_summary = {'resource_id': resource, 'revision': 1, 'model_id': model_id, 'schema_version': 7, 'name': name, 'elements': 0, 'relations': 0, 'migration': None}
|
||||
check(created == expected_summary and resource != model_id, 'complete create summary with distinct fresh resource/model IDs')
|
||||
etag = f'"p-{resource}-r1"'
|
||||
headers = {k.lower(): v for k, v in meta['headers'].items()}
|
||||
path = '/api/v1/projects/' + resource
|
||||
check(headers.get('etag') == etag and headers.get('location') == path, 'create ETag and resource Location')
|
||||
meta, raw = request(port, 'GET', path)
|
||||
stored = json.loads(raw)
|
||||
model = {'schema_version': 7, 'id': model_id, 'alias': 'mission', 'name': name, 'description': '', 'elements': {}, 'relations': {}, 'diagrams': {}, 'artifact_links': {}, 'traces': {}}
|
||||
source = f'arcadia 7;\nproject mission {json.dumps(name, ensure_ascii=False)} id "{model_id}" {{\n description "";\n}}\n'
|
||||
expected = {'resource_id': resource, 'revision': 1, 'model': model, 'source_project': {'entry': 'project.arcadia', 'files': {'project.arcadia': source}}}
|
||||
check(meta['status'] == 200 and stored == expected, 'GET equals independently predicted complete Project and SourceBundle')
|
||||
check({k.lower(): v for k, v in meta['headers'].items()}.get('etag') == etag, 'GET ETag equals created revision')
|
||||
disk = data_dir / (resource + '.json')
|
||||
disk_bytes = disk.read_bytes()
|
||||
check(json.loads(disk_bytes) == expected, 'durable disk envelope equals complete expected GET')
|
||||
check({p.name for p in data_dir.iterdir()} == {'.arcadia.lock', disk.name}, 'private store contains only one project and lock')
|
||||
stop(process)
|
||||
process = None
|
||||
process, port = start(2)
|
||||
meta, raw = request(port, 'GET', '/health/ready')
|
||||
check(meta['status'] == 200 and json.loads(raw) == {'status': 'ok'}, 'restarted packaged server ready')
|
||||
meta, raw = request(port, 'GET', path)
|
||||
check(meta['status'] == 200 and json.loads(raw) == expected, 'restart retains exact complete Project and SourceBundle')
|
||||
check({k.lower(): v for k, v in meta['headers'].items()}.get('etag') == etag and disk.read_bytes() == disk_bytes, 'restart retains revision, ETag and every durable envelope byte')
|
||||
RESULT['persistence'] = {'resource_id': resource, 'model_id': model_id, 'revision': 1, 'etag': etag, 'disk_sha256': sha(disk_bytes), 'expected': expected, 'stop_kind': 'explicit process termination after acknowledged durable create; not a graceful-drain claim'}
|
||||
finally:
|
||||
if process is not None and process.poll() is None:
|
||||
stop(process)
|
||||
check(file_map(PACKAGE) == prior['archive']['files'], 'server execution leaves complete extracted package unchanged')
|
||||
check(sha(ZIP.read_bytes()) == EXPECTED_ZIP, 'frozen ZIP unchanged after independent runtime checks')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
result_path = OUT / (sys.argv[1] + '-result.json')
|
||||
if result_path.exists():
|
||||
raise SystemExit('Refusing to overwrite prior evidence: ' + str(result_path))
|
||||
try:
|
||||
{'artifact': artifact, 'server': server}[sys.argv[1]]()
|
||||
RESULT['status'] = 'PASS'
|
||||
except Exception as error:
|
||||
RESULT['status'] = 'FAIL'
|
||||
RESULT['error'] = repr(error)
|
||||
RESULT['traceback'] = traceback.format_exc()
|
||||
RESULT['finished_utc'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
|
||||
RESULT['script_sha256'] = sha(Path(__file__).read_bytes())
|
||||
write_json(result_path, RESULT)
|
||||
print(json.dumps({k: RESULT[k] for k in ('scope', 'status', 'started_utc', 'finished_utc')}, indent=2))
|
||||
print('checks:', len(RESULT['checks']))
|
||||
if RESULT['status'] == 'FAIL':
|
||||
print(RESULT['traceback'])
|
||||
sys.exit(1)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 133 KiB |
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "ArcadiaStudio-0.2.0-preview.authoring.20260911-x86_64-pc-windows-gnullvm.zip",
|
||||
"bytes": 32618262,
|
||||
"sha256": "664f30b6217689aca1492eb263b3d15777067d732fa890eeb028f435209dadfa",
|
||||
"url": "http://217.177.34.108:3000/attachments/48a283b7-5391-4677-9427-02a772e5e7e4",
|
||||
"readback": "PASS"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "ArcadiaStudio-0.2.0-preview.authoring.20260911-x86_64-pc-windows-gnullvm.zip.sha256",
|
||||
"bytes": 143,
|
||||
"sha256": "ea553a85f35d2d48f229dd4cd00ac0c3d4a6e9dafa58273004b2c49d1279f856",
|
||||
"url": "http://217.177.34.108:3000/attachments/08164a39-9292-4b2d-9856-c6ec41d9b8e2",
|
||||
"readback": "PASS"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"id": 3,
|
||||
"name": "VALIDATION.md",
|
||||
"bytes": 29308,
|
||||
"sha256": "c2a70be735c780d3e468d5f12643fb00be75673434211366739926536bbbe9b9",
|
||||
"url": "http://217.177.34.108:3000/attachments/9ff53361-eedf-46a0-aa4e-34f11bb0f302",
|
||||
"readback": "PASS"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user