Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions build/release-evidence.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"schema_version": 2,
"module": {
"repository": "NDDev-it-com/nddev-zcode-app",
"setup_digest": "sha256:4ad6390371be5047622942d28039bd572b3e5345239f0013b535f6ac367e1f7e"
"setup_digest": "sha256:ae37209353cd6a10f43312c24941975d845587558619ade8f37ba55e535bfac5"
},
"harness": {
"repository": "NDDev-it-com/nddev-harnesses",
"commit": "489648ecab29e000683ab02c4d33fec00af05a98"
"commit": "a598102aff438239c8efcd2c790cd31356278267"
},
"adapter": {
"id": "zcode",
Expand All @@ -30,8 +30,8 @@
}
],
"lanes": [],
"generated_at_utc": "2026-07-17T15:23:49Z",
"expires_at_utc": "2027-01-13T15:23:49Z",
"generated_at_utc": "2026-07-17T18:03:49Z",
"expires_at_utc": "2027-01-13T18:03:49Z",
"promotion": {
"decision": "pending",
"waivers": []
Expand Down
8 changes: 7 additions & 1 deletion build/version.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"zcode_download_artifacts": {
"macos-arm64": {
"filename": "ZCode-3.3.6-mac-arm64.dmg",
"cdn_subpath": "macos-arm64",
"size_bytes": 161814493,
"sha512": "a469d3d9645efd92987727e39c1c04d7061d8ae38b105c1675896c1d0cbb5c193c1f9441d4abaf3f02b8df244cc6f66f05744b0830ee9edbb35ad1ebd7e9613b",
"team_id": "8A5X4JJ39T",
Expand All @@ -16,6 +17,7 @@
},
"macos-x64": {
"filename": "ZCode-3.3.6-mac-x64.dmg",
"cdn_subpath": "macos-x64",
"size_bytes": 170109451,
"sha512": "39b99df6c3efb1e41454c07d103d29273e45798f87579691bc6526b4da9a60da5f17252cc7e53ffa663a22ce703a6e180312abe747b0e27e86ce1e19c60a77dc",
"team_id": "8A5X4JJ39T",
Expand All @@ -25,11 +27,13 @@
},
"linux-x64-appimage": {
"filename": "ZCode-3.3.6-linux-x64.AppImage",
"cdn_subpath": "linux-x64",
"size_bytes": 154199981,
"sha512": "bf4e6c283b93aae3a474b06f872098e22fa4385183cbc7f4d13ec7d73119821c9c46362c06fa26fe6d9ef21f577c7981b7c2875f25b3e9ae042038a41853d549"
},
"linux-x64-deb": {
"filename": "ZCode-3.3.6-linux-x64.deb",
"cdn_subpath": "linux-x64",
"size_bytes": 114251860,
"sha512": "3704334817fe439151dd55b00044147802c747d8ef0e3453ecf24b550e222cec93c4f9bb2edf243106d05c8ecc27fc040d0d042b71410632505652620b82a0b3",
"package_name": "zcode",
Expand All @@ -38,11 +42,13 @@
},
"linux-arm64-appimage": {
"filename": "ZCode-3.3.6-linux-arm64.AppImage",
"cdn_subpath": "linux-arm64",
"size_bytes": 154132032,
"sha512": "44c6979a141556a35ee890bbfde8f4bc0c979983dbb0499b51240334edfb02703a74f7657a6c9415c1ed5eae07a3c22be40ce4895d801ee81636e5a5f08d3ee9"
},
"linux-arm64-deb": {
"filename": "ZCode-3.3.6-linux-arm64.deb",
"cdn_subpath": "linux-arm64",
"size_bytes": 108462636,
"sha512": "b5da4d16210e17597f02d508a1ed583b3a5e6b1ce169bb2b8a75f8885626311b86f8d8638199869235ea870b1e91b63ef851c063a86b086806de6be9640d0589",
"package_name": "zcode",
Expand All @@ -57,5 +63,5 @@
"wrapper_target": "${HOME}/.local/bin/zcode"
},
"schema": 2,
"_comment": "build_version is the semantic version of this nddev-zcode-app build. zcode_app_version and zcode_cli_version pin the exact ZCode desktop app and CLI versions this build was verified against. Every downloadable artifact is pinned by filename, byte size, and SHA-512; platform-native identity metadata is verified before installation where available. The CLI is the zcode.cjs inside the verified app bundle, launched via node through a wrapper script."
"_comment": "build_version is the semantic version of this nddev-zcode-app build. zcode_app_version and zcode_cli_version pin the exact ZCode desktop app and CLI versions this build was verified against. Every downloadable artifact is pinned by filename, byte size, and SHA-512; platform-native identity metadata is verified before installation where available. cdn_subpath is the exact per-artifact directory segment the official CDN serves the file under, between the version and the filename (macOS uses the platform-arch key; the two Linux formats share one linux-<arch> directory), so it is declared per artifact rather than derived from the key. The CLI is the zcode.cjs inside the verified app bundle, launched via node through a wrapper script."
}
12 changes: 9 additions & 3 deletions cli-tools/scripts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ if not isinstance(filename, str) or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._+-
raise SystemExit(f"invalid artifact filename: {key}")
if "/" in filename or "\\" in filename or any(ord(char) < 32 for char in filename):
raise SystemExit(f"artifact filename must be a basename: {key}")
cdn_subpath = artifact.get("cdn_subpath")
# A single lowercase path segment (e.g. macos-arm64, linux-x64). It sits between
# the version and the filename in the artifact URL, so it must not smuggle in
# extra path structure or traversal.
if not isinstance(cdn_subpath, str) or not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", cdn_subpath):
raise SystemExit(f"artifact cdn_subpath must be a lowercase path segment: {key}")
if not isinstance(digest, str) or not re.fullmatch(r"[0-9a-fA-F]{128}", digest):
raise SystemExit(f"artifact sha512 must be 128 hexadecimal characters: {key}")
if not isinstance(size, int) or isinstance(size, bool) or not 1 <= size <= 2 * 1024**3:
Expand Down Expand Up @@ -232,7 +238,7 @@ if launcher != expected_launcher:
raise SystemExit("zcode_cli_launcher must match the verified launcher contract exactly")

fields = (
base.rstrip("/"), filename, digest.lower(), str(size), team_id, bundle_id,
base.rstrip("/"), cdn_subpath, filename, digest.lower(), str(size), team_id, bundle_id,
bundle_version, package_name, package_arch, package_version,
launcher["linux_deb_entry"],
)
Expand All @@ -241,8 +247,8 @@ if any("|" in field or "\n" in field or "\r" in field for field in fields):
print("|".join(fields))
PY
)" || exit 1
IFS='|' read -r CDN_BASE artifact expected_sha512 expected_size TEAM_ID BUNDLE_ID BUNDLE_VERSION PACKAGE_NAME PACKAGE_ARCH PACKAGE_VERSION DEB_CLI_ENTRY <<< "$artifact_record"
url="${CDN_BASE}/${APP_VERSION}/${artifact}"
IFS='|' read -r CDN_BASE CDN_SUBPATH artifact expected_sha512 expected_size TEAM_ID BUNDLE_ID BUNDLE_VERSION PACKAGE_NAME PACKAGE_ARCH PACKAGE_VERSION DEB_CLI_ENTRY <<< "$artifact_record"
url="${CDN_BASE}/${APP_VERSION}/${CDN_SUBPATH}/${artifact}"

python3 -I - "$url" <<'PY'
import sys
Expand Down
16 changes: 15 additions & 1 deletion cli-tools/scripts/lib/version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,21 @@ import tempfile

command = sys.argv[1:]
limit = 64 * 1024
timeout = 3
# The postcondition probes the CLI immediately after install, so its 9 MB
# bundle is read cold from disk: measured cold start is 2-13 s (warm: <1 s),
# and a 3 s bound killed it mid-start, returning "unknown". Default to 30 s of
# headroom; allow an override for a slower host or a fast enforcement test,
# clamped so it can neither hang unbounded nor drop below a usable floor.
DEFAULT_TIMEOUT = 30
timeout = DEFAULT_TIMEOUT
_override = os.environ.get("NDDEV_ZCODE_PROBE_TIMEOUT_SECONDS")
if _override:
try:
_parsed = int(_override)
except ValueError:
_parsed = 0
if 1 <= _parsed <= 120:
timeout = _parsed

def constrain_output():
resource.setrlimit(resource.RLIMIT_FSIZE, (limit, limit))
Expand Down
13 changes: 13 additions & 0 deletions cli-tools/validate_public_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,19 @@ def check_artifacts(version: dict, errors: list[str]) -> None:
digest = str(entry.get("sha512", ""))
if _SHA512.fullmatch(digest) is None:
errors.append(f"{context}: sha512 must be 128 hex characters")
# The CDN serves each file under a per-artifact directory segment between
# the version and the filename. macOS uses the platform-arch key; the two
# Linux formats share one linux-<arch> directory. Pin the exact expected
# segment so a wrong path (the 404 this field exists to prevent) fails
# closed instead of only surfacing at download time.
expected_subpath = key[: -len("-appimage")] if key.endswith("-appimage") else key
expected_subpath = (
expected_subpath[: -len("-deb")]
if expected_subpath.endswith("-deb")
else expected_subpath
)
if entry.get("cdn_subpath") != expected_subpath:
errors.append(f"{context}: cdn_subpath must be {expected_subpath!r}")
if key.startswith("macos"):
if entry.get("app_version") != app:
errors.append(f"{context}: app_version must equal zcode_app_version")
Expand Down
Loading