feat(plugins): prepare isolated Rust 3 preview market
This commit is contained in:
1 parent
987b5ff2d8
commit
5d14bcffbc
22 files changed
+1612
-583
No files matched your search
@@ -0,0 +1,627 @@
|
||||
"""Assemble immutable Rust 3 plugin candidates from reviewed local inputs.
|
||||
|
||||
Only the small, dependency-free Go adapter is rebuilt. This script never runs
|
||||
Cargo, npm, a Cua session, a device tool, an installer, or a network download.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import stat
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path, PurePosixPath
|
||||
from urllib.parse import quote
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CUA = ROOT / "plugins/cua-driver"
|
||||
REVIEW = ROOT / "plugins/task-review"
|
||||
DEVICE_TEST = ROOT / "plugins/artemis-device-test"
|
||||
BASE_VERSION = "0.38.0+artemis.1"
|
||||
CUA_VERSION = "0.38.0+artemis.2"
|
||||
REVIEW_VERSION = "1.1.1"
|
||||
DEVICE_TEST_VERSION = "0.5.0+artemis.7"
|
||||
DEFAULT_RELEASE_TAG = "rust3-plugin-preview-20261101"
|
||||
HOST_RANGE = ">=3.0.0,<4.0.0"
|
||||
BASE_SHA256 = "c007da16d58c4165a1a344113e650e68bd92a69ad3ce4ca8285818c5426dc0f4"
|
||||
RELEASES = "https://git.playones.com/chenmingxuan/Artemis-Plugins/releases/download"
|
||||
BASE_URL = (
|
||||
f"{RELEASES}/cua-driver-v{BASE_VERSION}/cua-driver-{BASE_VERSION}-windows-amd64.zip"
|
||||
)
|
||||
RUNTIME = (
|
||||
"cua-driver.exe",
|
||||
"cua-driver-uia.exe",
|
||||
"cua_driver_sdk.dll",
|
||||
"cua-cursor-theme.exe",
|
||||
)
|
||||
ZIP_DATE = (2026, 11, 1, 0, 0, 0)
|
||||
|
||||
|
||||
def sha256(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def encoded(value):
|
||||
return (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
|
||||
|
||||
|
||||
def read_json(path):
|
||||
return json.loads(path.read_text(encoding="utf-8-sig"))
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def file_bytes(path):
|
||||
require(
|
||||
path.is_file() and not path.is_symlink(),
|
||||
f"Missing or linked input: {path.name}",
|
||||
)
|
||||
return path.read_bytes()
|
||||
|
||||
|
||||
def x64(data, name):
|
||||
require(len(data) >= 64 and data[:2] == b"MZ", f"Not a PE binary: {name}")
|
||||
offset = struct.unpack_from("<I", data, 0x3C)[0]
|
||||
require(
|
||||
offset + 6 <= len(data) and data[offset : offset + 4] == b"PE\0\0",
|
||||
f"Invalid PE: {name}",
|
||||
)
|
||||
require(
|
||||
struct.unpack_from("<H", data, offset + 4)[0] == 0x8664,
|
||||
f"Not Windows x64: {name}",
|
||||
)
|
||||
|
||||
|
||||
def exclusive(path, data):
|
||||
with path.open("xb") as destination:
|
||||
destination.write(data)
|
||||
|
||||
|
||||
def write_zip(path, entries):
|
||||
require(not path.exists(), f"Refusing to overwrite an archive: {path.name}")
|
||||
with zipfile.ZipFile(path, "x", compression=zipfile.ZIP_DEFLATED) as archive:
|
||||
for name, data in sorted(entries.items()):
|
||||
parts = PurePosixPath(name)
|
||||
require(
|
||||
not parts.is_absolute()
|
||||
and ".." not in parts.parts
|
||||
and "\\" not in name,
|
||||
"Unsafe archive entry",
|
||||
)
|
||||
info = zipfile.ZipInfo(name, date_time=ZIP_DATE)
|
||||
info.create_system = 3
|
||||
info.compress_type = zipfile.ZIP_DEFLATED
|
||||
info.external_attr = 0o100644 << 16
|
||||
archive.writestr(info, data)
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
require(
|
||||
archive.testzip() is None and set(archive.namelist()) == set(entries),
|
||||
"Archive readback failed",
|
||||
)
|
||||
for name, data in entries.items():
|
||||
require(
|
||||
sha256(archive.read(name)) == sha256(data),
|
||||
f"Archive input differs: {name}",
|
||||
)
|
||||
return sha256(path.read_bytes())
|
||||
|
||||
|
||||
def source_files(directory):
|
||||
files = sorted(directory.glob("*.go"))
|
||||
files += [directory / "go.mod", directory / "tools.json"]
|
||||
files += sorted((directory / "patches").glob("*.patch"))
|
||||
return {path.relative_to(directory).as_posix(): file_bytes(path) for path in files}
|
||||
|
||||
|
||||
def source_digest(files):
|
||||
return sha256(encoded({name: sha256(data) for name, data in sorted(files.items())}))
|
||||
|
||||
|
||||
def release_url(tag, filename):
|
||||
require(
|
||||
isinstance(tag, str)
|
||||
and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._+-]{0,127}", tag),
|
||||
"Release tag must be a single safe tag name",
|
||||
)
|
||||
require(PurePosixPath(filename).name == filename, "Invalid release filename")
|
||||
return f"{RELEASES}/{quote(tag, safe='')}/{quote(filename, safe='-._+')}"
|
||||
|
||||
|
||||
def device_test_files(directory):
|
||||
"""Freeze distributable bytes; the root SDK preflight owns market admission."""
|
||||
require(
|
||||
not directory.is_symlink() and not directory.is_junction(), "Linked plugin root"
|
||||
)
|
||||
directory = directory.resolve(strict=True)
|
||||
require(directory.is_dir(), "Device-test source must be a directory")
|
||||
entries, seen, total = {}, set(), 0
|
||||
forbidden = {
|
||||
"__pycache__",
|
||||
".git",
|
||||
"node_modules",
|
||||
".venv",
|
||||
"venv",
|
||||
".pytest_cache",
|
||||
".ruff_cache",
|
||||
}
|
||||
for path in sorted(directory.rglob("*")):
|
||||
name = path.relative_to(directory).as_posix()
|
||||
require(
|
||||
not path.is_symlink()
|
||||
and not path.is_junction()
|
||||
and path.resolve().is_relative_to(directory),
|
||||
f"Linked device-test input: {name}",
|
||||
)
|
||||
require(name.casefold() not in seen, "Case-colliding device-test inputs")
|
||||
seen.add(name.casefold())
|
||||
require(len(seen) <= 20000, "Too many device-test inputs")
|
||||
require(
|
||||
not any(
|
||||
part.casefold() in forbidden
|
||||
for part in path.relative_to(directory).parts
|
||||
),
|
||||
f"Non-distributable device-test input: {name}",
|
||||
)
|
||||
if path.is_dir():
|
||||
continue
|
||||
lower = path.name.casefold()
|
||||
require(
|
||||
stat.S_ISREG(path.stat().st_mode)
|
||||
and not lower.endswith((".pyc", ".pyo"))
|
||||
and lower != ".env"
|
||||
and not (
|
||||
lower.startswith(".env.")
|
||||
and not lower.endswith((".example", ".sample", ".template"))
|
||||
),
|
||||
f"Non-distributable device-test file: {name}",
|
||||
)
|
||||
data = file_bytes(path)
|
||||
total += len(data)
|
||||
require(total <= 512 * 1024 * 1024, "Device-test source exceeds size limit")
|
||||
entries[name] = data
|
||||
for name in (
|
||||
"plugin.json",
|
||||
"LICENSE",
|
||||
"SOURCE.md",
|
||||
"MARKET.json",
|
||||
"VERIFICATION.md",
|
||||
):
|
||||
require(
|
||||
bool(entries.get(name)),
|
||||
f"Missing device-test distribution material: {name}",
|
||||
)
|
||||
manifest = json.loads(entries["plugin.json"])
|
||||
require(
|
||||
manifest["id"] == "artemis-device-test"
|
||||
and manifest["version"] == DEVICE_TEST_VERSION
|
||||
and manifest["artemis_version"] == HOST_RANGE,
|
||||
"Unexpected device-test identity or compatibility",
|
||||
)
|
||||
require(bool(manifest.get("license", "").strip()), "Missing device-test license")
|
||||
market = json.loads(entries["MARKET.json"])
|
||||
hashes = {
|
||||
name: sha256(data) for name, data in entries.items() if name != "MARKET.json"
|
||||
}
|
||||
fingerprint = sha256(
|
||||
json.dumps(
|
||||
hashes, ensure_ascii=False, sort_keys=True, separators=(",", ":")
|
||||
).encode("utf-8")
|
||||
)
|
||||
require(
|
||||
market["plugin_id"] == manifest["id"]
|
||||
and market["plugin_version"] == manifest["version"]
|
||||
and market["content_sha256"] == fingerprint,
|
||||
"Device-test MARKET identity or content hash differs; rerun SDK preflight",
|
||||
)
|
||||
return manifest, entries
|
||||
|
||||
|
||||
def bundle_map(entries, filenames, catalog):
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"catalog": "catalog-v5.json",
|
||||
"catalog_sha256": sha256(catalog),
|
||||
"packages": [
|
||||
{
|
||||
"id": entry["manifest"]["id"],
|
||||
"version": entry["manifest"]["version"],
|
||||
"file": filename,
|
||||
"archive_url": entry["archive_url"],
|
||||
"sha256": entry["sha256"],
|
||||
}
|
||||
for entry, filename in zip(entries, filenames, strict=True)
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def check_contract(cua, review, sources):
|
||||
require(
|
||||
cua["id"] == "cua-driver" and cua["version"] == CUA_VERSION,
|
||||
"Unexpected Cua version",
|
||||
)
|
||||
require(
|
||||
review["id"] == "task-review" and review["version"] == REVIEW_VERSION,
|
||||
"Unexpected review version",
|
||||
)
|
||||
require(
|
||||
cua["artemis_version"] == review["artemis_version"] == HOST_RANGE,
|
||||
"Host compatibility differs",
|
||||
)
|
||||
require(
|
||||
cua["server"]
|
||||
== {
|
||||
"transport": "stdio",
|
||||
"command": "${plugin_dir}/bin/artemis-cua-adapter.exe",
|
||||
},
|
||||
"Unexpected Cua startup",
|
||||
)
|
||||
require(
|
||||
cua["resources"] == ["desktop:local"]
|
||||
and cua["targets"] == ["computer", "mixed"],
|
||||
"Cua resource scope differs",
|
||||
)
|
||||
require(
|
||||
re.search(
|
||||
rb'const adapterVersion = "' + re.escape(CUA_VERSION.encode()) + rb'"',
|
||||
sources["main.go"],
|
||||
),
|
||||
"MCP handshake version differs",
|
||||
)
|
||||
tools = json.loads(sources["tools.json"])
|
||||
actual = {tool["name"]: tool for tool in tools}
|
||||
declared = cua["capabilities"]["tools"]
|
||||
require(
|
||||
len(actual) == len(tools) and set(actual) == set(declared),
|
||||
"Tool manifest differs from embedded catalog",
|
||||
)
|
||||
for name, spec in declared.items():
|
||||
tool = actual[name]
|
||||
require(
|
||||
not spec.get("observation")
|
||||
or tool.get("annotations", {}).get("readOnlyHint") is True,
|
||||
"Observation is not read-only",
|
||||
)
|
||||
target = spec.get("target_argument")
|
||||
require(
|
||||
not target or target in tool["inputSchema"].get("properties", {}),
|
||||
"Target argument is absent",
|
||||
)
|
||||
require(
|
||||
review["schema_version"] == 3 and review["targets"] == [],
|
||||
"Review must remain declarative",
|
||||
)
|
||||
require(
|
||||
not review.get("server")
|
||||
and not review.get("skill")
|
||||
and not review["capabilities"]["tools"],
|
||||
"Review must not start a process",
|
||||
)
|
||||
require(
|
||||
{p["kind"] for p in review["workspace"]["panels"]} == {"original_case", "form"},
|
||||
"Review panels differ",
|
||||
)
|
||||
require(
|
||||
[f["id"] for f in review["workspace"]["fields"]]
|
||||
== [
|
||||
"execution_accuracy",
|
||||
"execution_note",
|
||||
"result_correctness",
|
||||
"result_note",
|
||||
],
|
||||
"Review field IDs differ",
|
||||
)
|
||||
|
||||
|
||||
def base_runtime(path, sources):
|
||||
data = file_bytes(path)
|
||||
require(sha256(data) == BASE_SHA256, "Published Cua base archive hash mismatch")
|
||||
with zipfile.ZipFile(io.BytesIO(data)) as base:
|
||||
names = base.namelist()
|
||||
require(len(names) == len(set(names)), "Duplicate base archive entries")
|
||||
require(
|
||||
all(
|
||||
not PurePosixPath(n).is_absolute()
|
||||
and ".." not in PurePosixPath(n).parts
|
||||
and "\\" not in n
|
||||
for n in names
|
||||
),
|
||||
"Unsafe base archive path",
|
||||
)
|
||||
manifest = json.loads(base.read("plugin.json"))
|
||||
require(
|
||||
manifest["version"] == BASE_VERSION and manifest["id"] == "cua-driver",
|
||||
"Wrong published base version",
|
||||
)
|
||||
for name, content in sources.items():
|
||||
previous = base.read("source/" + name).replace(b"\r\n", b"\n")
|
||||
expected = content.replace(b"\r\n", b"\n")
|
||||
if name == "main.go":
|
||||
expected = expected.replace(CUA_VERSION.encode(), BASE_VERSION.encode())
|
||||
require(
|
||||
previous == expected,
|
||||
f"Go behavior differs from the reviewed base: {name}",
|
||||
)
|
||||
runtime = {"bin/" + name: base.read("bin/" + name) for name in RUNTIME}
|
||||
provenance = json.loads(base.read("provenance.json"))
|
||||
require(
|
||||
provenance["plugin_version"] == BASE_VERSION,
|
||||
"Published provenance version differs",
|
||||
)
|
||||
for name, expected in provenance["compatibility_patch"][
|
||||
"rebuilt_binaries"
|
||||
].items():
|
||||
require(
|
||||
sha256(runtime["bin/" + name]) == expected,
|
||||
"Published runtime differs from provenance",
|
||||
)
|
||||
license_text = base.read("LICENSE.upstream")
|
||||
require(
|
||||
license_text == file_bytes(CUA / "LICENSE.upstream"),
|
||||
"Upstream license changed",
|
||||
)
|
||||
for name, binary in runtime.items():
|
||||
x64(binary, name)
|
||||
return runtime, provenance
|
||||
|
||||
|
||||
def build_adapter(go, output):
|
||||
build = output / ".build"
|
||||
env = {
|
||||
**os.environ,
|
||||
"CGO_ENABLED": "0",
|
||||
"GOOS": "windows",
|
||||
"GOARCH": "amd64",
|
||||
"GOTOOLCHAIN": "local",
|
||||
"GOENV": "off",
|
||||
"GOPROXY": "off",
|
||||
"GOSUMDB": "off",
|
||||
"GOTELEMETRY": "off",
|
||||
"GOFLAGS": "-mod=readonly",
|
||||
}
|
||||
for key, name in (
|
||||
("GOCACHE", "cache"),
|
||||
("GOMODCACHE", "modules"),
|
||||
("GOPATH", "path"),
|
||||
("GOTMPDIR", "temp"),
|
||||
):
|
||||
folder = build / name
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
env[key] = str(folder)
|
||||
version = subprocess.check_output(
|
||||
[str(go), "version"], env=env, text=True, timeout=30
|
||||
).strip()
|
||||
require(
|
||||
re.fullmatch(r"go version go[0-9.]+ windows/amd64", version),
|
||||
"Expected native, stable Windows amd64 Go",
|
||||
)
|
||||
commands = (
|
||||
["test", "-count=1", "./..."],
|
||||
["vet", "./..."],
|
||||
[
|
||||
"build",
|
||||
"-trimpath",
|
||||
"-buildvcs=false",
|
||||
"-ldflags=-s -w",
|
||||
"-o",
|
||||
str(build / "artemis-cua-adapter.exe"),
|
||||
".",
|
||||
],
|
||||
)
|
||||
for command in commands:
|
||||
subprocess.run([str(go), *command], cwd=CUA, env=env, check=True, timeout=300)
|
||||
binary = file_bytes(build / "artemis-cua-adapter.exe")
|
||||
x64(binary, "artemis-cua-adapter.exe")
|
||||
go_license = file_bytes(go.parent.parent / "LICENSE")
|
||||
return binary, go_license, version
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--cua-base-archive", type=Path, required=True)
|
||||
parser.add_argument("--go", type=Path, required=True)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="New empty candidate directory; never overwrite an earlier candidate",
|
||||
)
|
||||
parser.add_argument("--catalog-output", type=Path, default=ROOT / "catalog-v5.json")
|
||||
parser.add_argument("--device-test-source", type=Path, default=DEVICE_TEST)
|
||||
parser.add_argument("--release-tag", default=DEFAULT_RELEASE_TAG)
|
||||
args = parser.parse_args(argv)
|
||||
release_url(args.release_tag, "preflight.zip")
|
||||
output = args.output.resolve()
|
||||
require(
|
||||
not output.exists() or not any(output.iterdir()),
|
||||
"Candidate output must be new or empty",
|
||||
)
|
||||
require(
|
||||
not args.catalog_output.exists(),
|
||||
"Refusing to overwrite catalog-v5; use a new --catalog-output for a later candidate",
|
||||
)
|
||||
cua, review = read_json(CUA / "plugin.json"), read_json(REVIEW / "plugin.json")
|
||||
sources = source_files(CUA)
|
||||
check_contract(cua, review, sources)
|
||||
device_test, device_entries = device_test_files(args.device_test_source)
|
||||
runtime, previous = base_runtime(args.cua_base_archive.resolve(), sources)
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
adapter, go_license, go_version = build_adapter(args.go.resolve(), output)
|
||||
require(source_files(CUA) == sources, "Adapter source changed during build")
|
||||
require(
|
||||
device_test_files(args.device_test_source) == (device_test, device_entries),
|
||||
"Device-test source changed during build",
|
||||
)
|
||||
head = subprocess.check_output(
|
||||
["git", "rev-parse", "HEAD"], cwd=ROOT, text=True
|
||||
).strip()
|
||||
dirty = bool(
|
||||
subprocess.check_output(
|
||||
["git", "status", "--porcelain=v1", "--untracked-files=normal"], cwd=ROOT
|
||||
)
|
||||
)
|
||||
provenance = dict(previous)
|
||||
provenance.update(
|
||||
{
|
||||
"plugin_version": CUA_VERSION,
|
||||
"adapter_revision": 2,
|
||||
"host_compatibility": HOST_RANGE,
|
||||
"runtime_reuse": {
|
||||
"plugin_version": BASE_VERSION,
|
||||
"archive_url": BASE_URL,
|
||||
"archive_sha256": BASE_SHA256,
|
||||
"binaries": {name: sha256(data) for name, data in runtime.items()},
|
||||
},
|
||||
"adapter_build": {
|
||||
"go_version": go_version,
|
||||
"cgo_enabled": False,
|
||||
"sha256": sha256(adapter),
|
||||
"source_files_sha256": source_digest(sources),
|
||||
"checkout_commit_at_build": head,
|
||||
"checkout_dirty": dirty,
|
||||
"fixed_source_commit_claimed": False,
|
||||
},
|
||||
"verification_scope": "Go test/vet/build and archive contracts only. No Rust host handshake, UI, recording, device, business or performance validation in this build.",
|
||||
}
|
||||
)
|
||||
cua_entries = dict(runtime)
|
||||
cua_entries.update(
|
||||
{
|
||||
"bin/artemis-cua-adapter.exe": adapter,
|
||||
"LICENSE.Go": go_license,
|
||||
"provenance.json": encoded(provenance),
|
||||
}
|
||||
)
|
||||
for name in (
|
||||
"plugin.json",
|
||||
"SKILL.md",
|
||||
"LICENSE.upstream",
|
||||
"RELEASE.md",
|
||||
"VERIFICATION.md",
|
||||
"BUILD.md",
|
||||
):
|
||||
cua_entries[name] = file_bytes(CUA / name)
|
||||
cua_entries.update({"source/" + name: data for name, data in sources.items()})
|
||||
review_entries = {
|
||||
name: file_bytes(REVIEW / name)
|
||||
for name in (
|
||||
"plugin.json",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"VERIFICATION.md",
|
||||
"RELEASE.md",
|
||||
)
|
||||
}
|
||||
require(
|
||||
json.loads(cua_entries["plugin.json"]) == cua
|
||||
and json.loads(review_entries["plugin.json"]) == review,
|
||||
"Manifest changed during build",
|
||||
)
|
||||
cua_name = f"cua-driver-{CUA_VERSION}-windows-amd64.zip"
|
||||
review_name = f"task-review-{REVIEW_VERSION}.zip"
|
||||
device_name = f"artemis-device-test-{DEVICE_TEST_VERSION}-windows-amd64.zip"
|
||||
cua_sha = write_zip(output / cua_name, cua_entries)
|
||||
review_sha = write_zip(output / review_name, review_entries)
|
||||
device_sha = write_zip(output / device_name, device_entries)
|
||||
sums = f"{cua_sha} {cua_name}\n{review_sha} {review_name}\n{device_sha} {device_name}\n".encode(
|
||||
"ascii"
|
||||
)
|
||||
exclusive(output / "SHA256SUMS.txt", sums)
|
||||
entries = [
|
||||
{
|
||||
"manifest": cua,
|
||||
"release_stage": "preview",
|
||||
"archive_url": release_url(args.release_tag, cua_name),
|
||||
"sha256": cua_sha,
|
||||
"verification": "Windows x64 / ARTEMIS 3.x 候选。Go 确定性回归、vet、桥接构建和最终 ZIP 输入/回读校验通过;Driver/UIA/SDK/游标工具原样复用已发布 0.38.0+artemis.1 的固定文件与来源。3.0 实际握手、窗口发现、截图、动作、录像及业务任务待宿主统一验收;未进行性能测试。",
|
||||
},
|
||||
{
|
||||
"manifest": review,
|
||||
"release_stage": "preview",
|
||||
"archive_url": release_url(args.release_tag, review_name),
|
||||
"sha256": review_sha,
|
||||
"verification": "ARTEMIS 3.x 声明式工作区候选,无运行进程或额外依赖。清单字段/面板及最终 ZIP 回读校验通过;实际界面、自动保存、导航与报告导出待 3.0 宿主统一验收,不沿用 2.4 的历史测试结论。",
|
||||
},
|
||||
{
|
||||
"manifest": device_test,
|
||||
"release_stage": "preview",
|
||||
"archive_url": release_url(args.release_tag, device_name),
|
||||
"sha256": device_sha,
|
||||
"verification": "ARTEMIS 3.x Windows x64 候选;独立 Python/MCP/dotenv 与外部工具由使用者准备。固定源码、MARKET 内容摘要和最终 ZIP 回读校验;完整许可及证据准入由 SDK 独立预检。Rust 安装、握手、取消、设备与业务验收范围以包内验证记录为准,不沿用旧宿主结论。",
|
||||
},
|
||||
]
|
||||
catalog = encoded({"schema_version": 1, "plugins": entries})
|
||||
exclusive(output / "catalog-v5.candidate.json", catalog)
|
||||
exclusive(output / "catalog-v5.json", catalog)
|
||||
mapping = encoded(
|
||||
bundle_map(entries, [cua_name, review_name, device_name], catalog)
|
||||
)
|
||||
exclusive(output / "bundle-map.json", mapping)
|
||||
exclusive(args.catalog_output.resolve(), catalog)
|
||||
receipt = {
|
||||
"schema_version": 1,
|
||||
"candidate_only": True,
|
||||
"published": False,
|
||||
"release_tag": args.release_tag,
|
||||
"python_version": platform.python_version(),
|
||||
"go_version": go_version,
|
||||
"source_commit_at_build": head,
|
||||
"source_dirty": dirty,
|
||||
"adapter_source_sha256": source_digest(sources),
|
||||
"device_test_source_sha256": source_digest(device_entries),
|
||||
"base_archive_sha256": BASE_SHA256,
|
||||
"archives": [
|
||||
{"file": cua_name, "sha256": cua_sha},
|
||||
{"file": review_name, "sha256": review_sha},
|
||||
{"file": device_name, "sha256": device_sha},
|
||||
],
|
||||
"catalog_sha256": sha256(catalog),
|
||||
"bundle_map_sha256": sha256(mapping),
|
||||
"checks": [
|
||||
"go test -count=1 ./...",
|
||||
"go vet ./...",
|
||||
"go build",
|
||||
"manifest/tools/source/runtime contract",
|
||||
"final ZIP readback",
|
||||
"device-test MARKET identity/content hash (SDK admission is separate)",
|
||||
],
|
||||
"not_run": [
|
||||
"Cargo",
|
||||
"npm",
|
||||
"Rust host handshake",
|
||||
"device/UI/recording/business/performance tests",
|
||||
],
|
||||
}
|
||||
exclusive(output / "build-receipt.json", encoded(receipt))
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"candidate_directory": str(output),
|
||||
"archives": receipt["archives"],
|
||||
"catalog": str(args.catalog_output.resolve()),
|
||||
"published": False,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (
|
||||
ValueError,
|
||||
OSError,
|
||||
subprocess.SubprocessError,
|
||||
zipfile.BadZipFile,
|
||||
KeyError,
|
||||
) as error:
|
||||
sys.exit(f"Candidate assembly failed: {error}")
|
||||
Reference in new issue
Block a user