From e5077c12eeb11dd71b373227206093b86c5a42a0 Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Tue, 14 Jul 2026 09:46:56 +0700 Subject: [PATCH 1/5] feat: advance verified ZCode runtime to official 3.3.5 with derived artifact identities --- .gds/repository.yaml | 7 + CHANGELOG.md | 25 ++ README.md | 2 +- VERSION | 2 +- build/manifest.json | 2 +- build/version.json | 52 ++-- cli-tools/validate_public_contracts.py | 246 ++++++++++++++++++ docs/architecture.md | 2 +- docs/secrets.md | 2 +- references/zcode-baseline.json | 14 +- .../nddev-builder/marketplace.json | 2 +- .../plugins/core/.zcode-plugin/plugin.json | 2 +- 12 files changed, 318 insertions(+), 40 deletions(-) create mode 100644 cli-tools/validate_public_contracts.py diff --git a/.gds/repository.yaml b/.gds/repository.yaml index 9dffcd8..c89f25b 100644 --- a/.gds/repository.yaml +++ b/.gds/repository.yaml @@ -35,6 +35,13 @@ git: handoff_pr: "preferred" cleanup: "merged-only" +verification: + commands: + test: + - "python3 cli-tools/validate_public_contracts.py" + required: + - "test" + agent: context_profile: "public-module" generated_agents: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d7179..2ec56e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,31 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [2.1.3] - 2026-07-14 + +### Changed + +- Verified ZCode runtime advanced to the official app 3.3.5 (build 3.3.5.3027, + released 2026-07-13): all six download artifacts re-pinned by independently + derived filename, byte size, and SHA-512; macOS bundle identity, Developer ID + signature, and notarization re-verified; Debian package version 3.3.5-3027; + embedded CLI remains 0.15.2 (rebuilt bundle, byte-identical across all six + artifacts). +- `build/release-evidence.json` upgraded to execution-bound schema version 2: + promotion starts as `pending` and becomes `approved` only from real per-lane + CI result records plus a vendor-currentness observation; approval is never a + default. + +### Added + +- `cli-tools/validate_public_contracts.py`: repository-owned public fast + verification (version lockstep, artifact identity shape, baseline + cross-references, marketplace/plugin catalog integrity) declared in + `.gds/repository.yaml` as the required test command. +- GDS generated projections (`AGENTS.md`, `.claude/CLAUDE.md`, + `.gds/bundle.lock.yaml`, `.gds/compiled-policy.json`) refreshed from bundle + source 7120d79. + ### Added - Machine-readable per-platform support tiers in diff --git a/README.md b/README.md index d83f078..393b78d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ changes. - **Author:** Danil Silantyev (github:rldyourmnd), CEO NDDev - **License:** AGPL-3.0-or-later - **Build version:** 2.1.2 -- **Verified ZCode runtime:** app 3.3.4, CLI 0.15.2, model GLM-5.2 +- **Verified ZCode runtime:** app 3.3.5, CLI 0.15.2, model GLM-5.2 ## What this repository contains diff --git a/VERSION b/VERSION index eca07e4..ac2cdeb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.2 +2.1.3 diff --git a/build/manifest.json b/build/manifest.json index b8de3d8..0d2ec43 100644 --- a/build/manifest.json +++ b/build/manifest.json @@ -1,6 +1,6 @@ { "name": "nddev-zcode-app", - "build_version": "2.1.2", + "build_version": "2.1.3", "source_root": "zcode_tools/marketplaces/", "installer": "cli-tools/scripts/install.sh install --setup ", "layout": { diff --git a/build/version.json b/build/version.json index eebbe03..0224ebf 100644 --- a/build/version.json +++ b/build/version.json @@ -1,53 +1,53 @@ { - "build_version": "2.1.2", - "zcode_app_version": "3.3.4", + "build_version": "2.1.3", + "zcode_app_version": "3.3.5", "zcode_cli_version": "0.15.2", "zcode_runtime": "GLM-5.2", "zcode_cdn_base": "https://cdn-zcode.z.ai/zcode/electron/releases", "zcode_download_artifacts": { "macos-arm64": { - "filename": "ZCode-3.3.4-mac-arm64.dmg", - "size_bytes": 161523919, - "sha512": "41d4d7bba64d7db8a12c5dbc8808cc44cefc67c43b84c8d7622d32a132bbbefac4af08724427223b807ad202f12863f3d310b01f1c2c12200ba6ff463db6513c", + "filename": "ZCode-3.3.5-mac-arm64.dmg", + "size_bytes": 161593509, + "sha512": "a1124586b9c95ebcc1c42d0a90f415f11a139e35d4b5799c07ffe663997fd9658ad4f808e60ef4c02095c42337d466a8291a4eeb47052e6a6445df2212bc075e", "team_id": "8A5X4JJ39T", "bundle_id": "dev.zcode.app", - "app_version": "3.3.4", - "bundle_version": "3.3.4.2877" + "app_version": "3.3.5", + "bundle_version": "3.3.5.3027" }, "macos-x64": { - "filename": "ZCode-3.3.4-mac-x64.dmg", - "size_bytes": 169797960, - "sha512": "8e20c7d9534ac34775a027252b492ef854b62a30ba83d63d677028476d76e07be4159ea3792e1fe2300384a32d0f500ef4d23955b1202cc661df41a03ac1fe96", + "filename": "ZCode-3.3.5-mac-x64.dmg", + "size_bytes": 169853442, + "sha512": "c9d3372674d6a0d5c4ddf5c4d4b329c3321e1c038be21efefc4afe04e2bf4a7e76f59e85c86d7ef00200a878a58c72c71567757d9e448aede4b07a98125bb79f", "team_id": "8A5X4JJ39T", "bundle_id": "dev.zcode.app", - "app_version": "3.3.4", - "bundle_version": "3.3.4.2877" + "app_version": "3.3.5", + "bundle_version": "3.3.5.3027" }, "linux-x64-appimage": { - "filename": "ZCode-3.3.4-linux-x64.AppImage", - "size_bytes": 153909304, - "sha512": "5dcc5c0e948ca558f653410a3dfc1f08766b688cace98c671492690b0a7d9e1293900465ab0714ca73d6ea37cea56b05302191344679b95c1da49c322c9aeb31" + "filename": "ZCode-3.3.5-linux-x64.AppImage", + "size_bytes": 153946162, + "sha512": "48bdd36eec889fb2c4a91acdae1f5f494cfe0855da5234f471fe2955793f735191239ffdb80eda1e25dd8d33336d00d5afb2b6e8f7c4c4dc7ab9fef30c383da2" }, "linux-x64-deb": { - "filename": "ZCode-3.3.4-linux-x64.deb", - "size_bytes": 113797900, - "sha512": "5e26ded32c9f2fdc5da70805bd68f78588e48c6dcc785fec276a75d4f2a6a6b2423b93f880889eba36e2a6e60bd97e0c07039db230c769804b65ace09e6fe905", + "filename": "ZCode-3.3.5-linux-x64.deb", + "size_bytes": 114001880, + "sha512": "94ccf3e59ccd505a411783a8ec232c103c6e9786992813b0642932448808bf6ab8de4a3789fbdf00de1eb0d3be2977367f5d58733671396209cdea7e5e3f8b74", "package_name": "zcode", "package_arch": "amd64", - "package_version": "3.3.4-2877" + "package_version": "3.3.5-3027" }, "linux-arm64-appimage": { - "filename": "ZCode-3.3.4-linux-arm64.AppImage", - "size_bytes": 153841311, - "sha512": "a1c8c759cad74f0f0be5ce491ce234cdb6db03a476ebadc2a8ca1c6d72a5490603686e207fe2eb14930d6fadda3c4c1f311b62d0da6ad09265e86459c38c8734" + "filename": "ZCode-3.3.5-linux-arm64.AppImage", + "size_bytes": 153878161, + "sha512": "6383bc9337ab2bb57e4f71aa7ad704bc7ec2d32c22659119f7f0a1e5ca8382049ed408826109550916a2f4e382e34e8e70359dff89bdb5c2436285fc20f686d9" }, "linux-arm64-deb": { - "filename": "ZCode-3.3.4-linux-arm64.deb", - "size_bytes": 108009364, - "sha512": "bab1fa5ab17dc58a6b386151c2ce7682ef06ffaa74723f8dc2a3987b15e3874da0643ee59b9fbe0986115f510962f9535f92e06232ae64ce51018e9d56e57954", + "filename": "ZCode-3.3.5-linux-arm64.deb", + "size_bytes": 108207160, + "sha512": "cd81ccc1962d7a0f5f910d86b7cb1436b7643c851ad5e11a002f04a469d8c7ccfec00d9630152e1f01dfb3ce279c38029331258e6dc2fb6451ce774246a40288", "package_name": "zcode", "package_arch": "arm64", - "package_version": "3.3.4-2877" + "package_version": "3.3.5-3027" } }, "zcode_cli_launcher": { diff --git a/cli-tools/validate_public_contracts.py b/cli-tools/validate_public_contracts.py new file mode 100644 index 0000000..9e51842 --- /dev/null +++ b/cli-tools/validate_public_contracts.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +"""Validate the public NDDev ZCode module contracts without private inputs. + +This is the repository-owned fast verification entry point declared in +`.gds/repository.yaml`. It checks only tracked public contract files and +never reads private harness material, user state, or the network. +""" + +from __future__ import annotations + +import datetime as dt +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + +ARTIFACT_KEYS = ( + "macos-arm64", + "macos-x64", + "linux-x64-appimage", + "linux-x64-deb", + "linux-arm64-appimage", + "linux-arm64-deb", +) +_SHA512 = re.compile(r"[0-9a-f]{128}") + + +def load_json(relative: str, errors: list[str]) -> dict | None: + path = ROOT / relative + if not path.is_file(): + errors.append(f"missing required contract file: {relative}") + return None + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc: + errors.append(f"{relative}: unreadable or invalid JSON: {exc}") + return None + if not isinstance(data, dict): + errors.append(f"{relative}: top-level value must be an object") + return None + return data + + +def parse_utc(value: object) -> dt.datetime | None: + if not isinstance(value, str) or not value.endswith("Z"): + return None + try: + return dt.datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return None + + +def check_artifacts(version: dict, errors: list[str]) -> None: + app = str(version.get("zcode_app_version", "")) + artifacts = version.get("zcode_download_artifacts") + if not isinstance(artifacts, dict): + errors.append("build/version.json: zcode_download_artifacts must be an object") + return + if sorted(artifacts) != sorted(ARTIFACT_KEYS): + errors.append( + "build/version.json: artifact set must be exactly " + f"{sorted(ARTIFACT_KEYS)}, found {sorted(artifacts)}" + ) + bundle_versions: set[str] = set() + package_versions: set[str] = set() + for key, entry in artifacts.items(): + context = f"build/version.json:zcode_download_artifacts.{key}" + if not isinstance(entry, dict): + errors.append(f"{context}: must be an object") + continue + filename = str(entry.get("filename", "")) + if f"-{app}-" not in f"-{filename}-".replace("ZCode-", "-", 1): + if app not in filename: + errors.append(f"{context}: filename does not embed app version {app}") + size = entry.get("size_bytes") + if not isinstance(size, int) or size <= 0: + errors.append(f"{context}: size_bytes must be a positive integer") + digest = str(entry.get("sha512", "")) + if _SHA512.fullmatch(digest) is None: + errors.append(f"{context}: sha512 must be 128 hex characters") + if key.startswith("macos"): + if entry.get("app_version") != app: + errors.append(f"{context}: app_version must equal zcode_app_version") + bundle = str(entry.get("bundle_version", "")) + if not bundle.startswith(f"{app}."): + errors.append(f"{context}: bundle_version must extend app version {app}") + bundle_versions.add(bundle) + for field in ("team_id", "bundle_id"): + if not str(entry.get(field, "")).strip(): + errors.append(f"{context}: {field} must be a non-empty string") + if key.endswith("deb"): + package = str(entry.get("package_version", "")) + if not package.startswith(f"{app}-"): + errors.append(f"{context}: package_version must extend app version {app}") + package_versions.add(package) + if entry.get("package_name") != "zcode": + errors.append(f"{context}: package_name must be zcode") + if len(bundle_versions) > 1: + errors.append("build/version.json: macOS bundle_version values disagree") + if len(package_versions) > 1: + errors.append("build/version.json: Debian package_version values disagree") + + +def check_baseline(version: dict, baseline: dict, errors: list[str]) -> None: + zcode = baseline.get("zcode") + if not isinstance(zcode, dict): + errors.append("references/zcode-baseline.json: zcode must be an object") + return + pairs = ( + ("app_version", "zcode_app_version"), + ("cli_version", "zcode_cli_version"), + ("runtime_model", "zcode_runtime"), + ) + for baseline_key, version_key in pairs: + if zcode.get(baseline_key) != version.get(version_key): + errors.append( + f"references/zcode-baseline.json: zcode.{baseline_key} disagrees with " + f"build/version.json:{version_key}" + ) + app = str(version.get("zcode_app_version", "")) + if not str(zcode.get("app_build", "")).startswith(f"{app}."): + errors.append("references/zcode-baseline.json: zcode.app_build must extend app_version") + if not str(zcode.get("linux_deb_package_version", "")).startswith(f"{app}-"): + errors.append( + "references/zcode-baseline.json: zcode.linux_deb_package_version must extend app_version" + ) + support = baseline.get("platform_support") + if not isinstance(support, dict): + errors.append("references/zcode-baseline.json: platform_support must be an object") + return + platforms = support.get("platforms") + if not isinstance(platforms, dict) or sorted(platforms) != ["macos", "ubuntu"]: + errors.append( + "references/zcode-baseline.json: platform_support.platforms must define macos and ubuntu" + ) + verified = parse_utc(support.get("verified_at_utc")) + expires = parse_utc(support.get("expires_at_utc")) + if verified is None or expires is None or verified >= expires: + errors.append( + "references/zcode-baseline.json: platform_support verified/expiry window is invalid" + ) + + +def check_marketplaces(errors: list[str]) -> None: + marketplaces_root = ROOT / "zcode_tools" / "marketplaces" + catalog = ( + sorted(p for p in marketplaces_root.iterdir() if p.is_dir()) + if marketplaces_root.is_dir() + else [] + ) + if not catalog: + errors.append("zcode_tools/marketplaces/: no marketplace setups found") + for marketplace_dir in catalog: + relative = marketplace_dir.relative_to(ROOT).as_posix() + manifest = load_json(f"{relative}/marketplace.json", errors) + if manifest is None: + continue + if manifest.get("name") != marketplace_dir.name: + errors.append(f"{relative}/marketplace.json: name must equal directory name") + plugins = manifest.get("plugins") + if not isinstance(plugins, list): + errors.append(f"{relative}/marketplace.json: plugins must be an array") + continue + for entry in plugins: + if not isinstance(entry, dict): + errors.append(f"{relative}/marketplace.json: plugin entries must be objects") + continue + source = str(entry.get("source", "")) + if not source.startswith("./"): + errors.append( + f"{relative}/marketplace.json: plugin source must be relative: {source}" + ) + continue + plugin_dir = marketplace_dir / source[2:] + if not plugin_dir.is_dir(): + errors.append(f"{relative}/marketplace.json: plugin source missing: {source}") + continue + plugin_manifest = plugin_dir / ".zcode-plugin" / "plugin.json" + if plugin_manifest.is_file(): + plugin_relative = plugin_manifest.relative_to(ROOT).as_posix() + plugin = load_json(plugin_relative, errors) + if plugin is not None and not str(plugin.get("name", "")).strip(): + errors.append(f"{plugin_relative}: name must be a non-empty string") + + +def main() -> int: + errors: list[str] = [] + + version = load_json("build/version.json", errors) + baseline = load_json("references/zcode-baseline.json", errors) + manifest = load_json("build/manifest.json", errors) + evidence = load_json("build/release-evidence.json", errors) + + version_file = ROOT / "VERSION" + declared = version_file.read_text(encoding="utf-8").strip() if version_file.is_file() else None + if declared is None: + errors.append("missing VERSION file") + + if version is not None: + if declared is not None and version.get("build_version") != declared: + errors.append( + "VERSION and build/version.json:build_version disagree: " + f"{declared!r} != {version.get('build_version')!r}" + ) + check_artifacts(version, errors) + if baseline is not None: + check_baseline(version, baseline, errors) + builder = load_json( + "zcode_tools/marketplaces/nddev-builder/plugins/core/.zcode-plugin/plugin.json", errors + ) + if builder is not None and builder.get("version") != version.get("build_version"): + errors.append( + "nddev-builder core plugin version disagrees with build/version.json:build_version" + ) + + if manifest is not None and version is not None: + if manifest.get("build_version") != version.get("build_version"): + errors.append("build/manifest.json:build_version disagrees with build/version.json") + + if evidence is not None: + if evidence.get("schema_version") != 2: + errors.append("build/release-evidence.json: schema_version must be 2") + decision = evidence.get("promotion", {}) + if not isinstance(decision, dict) or decision.get("decision") not in { + "approved", + "pending", + }: + errors.append( + "build/release-evidence.json: promotion.decision must be approved or pending" + ) + + check_marketplaces(errors) + + if errors: + print(f"validate_public_contracts.py: FAIL ({len(errors)} error(s))") + for item in errors: + print(f" - {item}") + return 1 + print("validate_public_contracts.py: PASS") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/architecture.md b/docs/architecture.md index a0b104f..537cc1a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -61,7 +61,7 @@ prefix. tools come from the active workspace. All preference templates keep `modelProviderFamilyModes.zai` set to `oauth`, -which is the verified ZCode 3.3.4 account-authentication mode. The provider +which is the verified ZCode 3.3.5 account-authentication mode. The provider objects in `v2/config.json` are a separate explicit API-key contract: Z.ai uses `https://api.z.ai/api/anthropic`; BigModel uses `https://open.bigmodel.cn/api/anthropic`. Both API-key providers are disabled diff --git a/docs/secrets.md b/docs/secrets.md index e550bb3..c629599 100644 --- a/docs/secrets.md +++ b/docs/secrets.md @@ -34,7 +34,7 @@ $EDITOR build/.env Z.ai account authentication and explicit provider API keys are separate: - `modelProviderFamilyModes.zai: oauth` in `v2/setting.json` is the verified - ZCode 3.3.4 default for account login. It does not require `ZAI_API_KEY`. + ZCode 3.3.5 default for account login. It does not require `ZAI_API_KEY`. - `ZAI_API_KEY` configures the explicit custom Z.ai provider at `https://api.z.ai/api/anthropic` under `custom:zai-api-key`. That provider is disabled by default; set its `enabled` field to `true` only after supplying diff --git a/references/zcode-baseline.json b/references/zcode-baseline.json index 1b9689e..03498c3 100644 --- a/references/zcode-baseline.json +++ b/references/zcode-baseline.json @@ -2,9 +2,9 @@ "baseline_version": 2, "_comment": "ZCode runtime baseline for this build. The app and CLI versions are pinned in build/version.json, compared during setup installation, and enforced as strict bootstrap postconditions. CLI 0.15.2 requires an explicit provider/model reference and matching provider base URL before desktop session creation. Account-mode Z.ai authentication uses the verified OAuth preference; explicit API-key providers are separate custom provider entries. Update verified_date and versions when the baseline changes.", "zcode": { - "app_version": "3.3.4", - "app_build": "3.3.4.2877", - "linux_deb_package_version": "3.3.4-2877", + "app_version": "3.3.5", + "app_build": "3.3.5.3027", + "linux_deb_package_version": "3.3.5-3027", "cli_version": "0.15.2", "app_bundle_id": "dev.zcode.app", "macos_team_id": "8A5X4JJ39T", @@ -43,8 +43,8 @@ }, "platform_support": { "_comment": "Explicit vendor and NDDev support tiers per installer platform with a freshness window. macOS is a vendor general-availability download; upstream ZCode Linux ships only through the vendor Linux beta group, so Ubuntu is NDDev-supported on top of a vendor beta. Re-verify the vendor tiers and refresh verified_at_utc/expires_at_utc before expiry.", - "verified_at_utc": "2026-07-10T00:00:00Z", - "expires_at_utc": "2027-01-06T00:00:00Z", + "verified_at_utc": "2026-07-14T00:00:00Z", + "expires_at_utc": "2027-01-10T00:00:00Z", "platforms": { "macos": { "vendor_support_tier": "stable", @@ -60,6 +60,6 @@ } } }, - "verified_date": "2026-07-10", - "verified_against": "ZCode app 3.3.4 (build 3.3.4.2877), CLI 0.15.2 config parser and desktop model-selection adapter, all six native distribution artifacts, and the task/session storage layout" + "verified_date": "2026-07-14", + "verified_against": "ZCode app 3.3.5 (build 3.3.5.3027), CLI 0.15.2 config parser and desktop model-selection adapter, all six native distribution artifacts, and the task/session storage layout" } diff --git a/zcode_tools/marketplaces/nddev-builder/marketplace.json b/zcode_tools/marketplaces/nddev-builder/marketplace.json index c5759af..8bc074e 100644 --- a/zcode_tools/marketplaces/nddev-builder/marketplace.json +++ b/zcode_tools/marketplaces/nddev-builder/marketplace.json @@ -10,7 +10,7 @@ "name": "core", "source": "./plugins/core", "description": "Reusable toolkit: skills, slash commands, and a reviewer subagent for building plugins, managing MCP servers and providers, authoring skills, and listing or removing components.", - "version": "2.1.2", + "version": "2.1.3", "author": { "name": "Danil Silantyev (github:rldyourmnd), CEO NDDev" }, diff --git a/zcode_tools/marketplaces/nddev-builder/plugins/core/.zcode-plugin/plugin.json b/zcode_tools/marketplaces/nddev-builder/plugins/core/.zcode-plugin/plugin.json index db98c60..b11b2ce 100644 --- a/zcode_tools/marketplaces/nddev-builder/plugins/core/.zcode-plugin/plugin.json +++ b/zcode_tools/marketplaces/nddev-builder/plugins/core/.zcode-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "core", - "version": "2.1.2", + "version": "2.1.3", "description": "Reusable ZCode toolkit: skills, slash commands, and a reviewer subagent for building plugins, managing MCP servers and providers, authoring skills, and listing or removing components.", "author": { "name": "Danil Silantyev (github:rldyourmnd), CEO NDDev", From f2eac5eaf53e52b2291d8e2d04c24df514a3db19 Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Tue, 14 Jul 2026 09:47:49 +0700 Subject: [PATCH 2/5] chore(gds): refresh generated projections to bundle source 7120d79 --- .claude/CLAUDE.md | 9 +++---- .gds/bundle.lock.yaml | 14 +++++----- .gds/compiled-policy.json | 54 ++++----------------------------------- AGENTS.md | 8 +++--- 4 files changed, 20 insertions(+), 65 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 7c972f2..085e1fa 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -2,9 +2,9 @@ GENERATED FILE - DO NOT EDIT DIRECTLY generator: gds bundle: 0.1.0-dev -source-commit: 06dd4355b03d600bbf40eee21e86fb9e424d42c5 -input-digest: sha256:229eda43354f1690ccec8d8070eb8392b3072c1ab9ff80b451ce500a5d8867cb -output-digest: sha256:8e2e6d165d8bdeca5c3f47f18ebd2a8778bf3e9f7979d468b988019506c89d6b +source-commit: 7120d79f77124632c6cf355bca27598c120c1182 +input-digest: sha256:24879f858628a87b9316a2f7a91d090c53b39723491abc207f8dec44d4eaab95 +output-digest: sha256:d63e25cbe4ea712b5562cc5e7c663ba33a6ca21a6f299c25d4cff88636a55c18 edit-source: - .gds/repository.yaml - policies/base/repository-default.yaml @@ -42,8 +42,7 @@ edit-source: ## Verification commands -- No repository-owned verification command is declared; report it as - `NOT_PROVEN`. +- Test: `python3 cli-tools/validate_public_contracts.py`. ## Claude workflow routing diff --git a/.gds/bundle.lock.yaml b/.gds/bundle.lock.yaml index 5b22b19..21d788d 100644 --- a/.gds/bundle.lock.yaml +++ b/.gds/bundle.lock.yaml @@ -5,16 +5,16 @@ bundle: version: "0.1.0-dev" release_sequence: 0 channel: "development" - source_commit: "06dd4355b03d600bbf40eee21e86fb9e424d42c5" - digest: "sha256:82c50767f8891f2235905270a8f3f98fa10f56a2d5aac1db768f3f8b522606b7" + source_commit: "7120d79f77124632c6cf355bca27598c120c1182" + digest: "sha256:7dd8c40b344edf75b465f692dc44340b1dda8a187a5326fe703aef0aabeeb1c1" projection: - input_digest: "sha256:229eda43354f1690ccec8d8070eb8392b3072c1ab9ff80b451ce500a5d8867cb" - output_digest: "sha256:701ff062593f8e74e75e290c12ab20bee00aed5cf63195d5df7f783b8194d000" + input_digest: "sha256:24879f858628a87b9316a2f7a91d090c53b39723491abc207f8dec44d4eaab95" + output_digest: "sha256:3456bc3372b26a41206804d6cf4dbf39cb57b84ebf16682871b08e02dda4039c" files: - path: ".claude/CLAUDE.md" - digest: "sha256:b62ac7223e22c7618e349f85c069993e529796af30b925182c04d25e7aaecc30" + digest: "sha256:bc7c9a870bfdb8b6cd6d32935cbe793ef4365600fdcd0a5b49d910f547c2536c" - path: ".gds/compiled-policy.json" - digest: "sha256:226b256afbe7b17f854ee3260e5d7a858cbb67f478c94525ceb7a656fa473f08" + digest: "sha256:eeb034a7afa169596f397691f12b9f08b8a963e72f1c07d8db08dc9f746cc023" - path: "AGENTS.md" - digest: "sha256:24a10b8af5fc20a4e5209b768c6c78dfd07c96b3a6028e2aff159abf18ed32ea" + digest: "sha256:e976a2bd9f616aa5a2ba101272b0f64f8e28012f51880717898244555843d380" diff --git a/.gds/compiled-policy.json b/.gds/compiled-policy.json index 4a457de..030b13f 100644 --- a/.gds/compiled-policy.json +++ b/.gds/compiled-policy.json @@ -3,7 +3,7 @@ "compiled_policy": { "repository_id": "repo_01KX8PR71HN6XNJ3VN5SQCB0TF", "bundle_version": "0.1.0-dev", - "digest": "sha256:5fca177339e8c6da97053eda3688758a6d039a08d03a93f56a1c97ee47abe6e1" + "digest": "sha256:55be68d3e3e69c161c90a16276a2617faef824083296d4300fc20d3eb1fb90c5" }, "sources": [ { @@ -12,7 +12,7 @@ "priority": 100, "distribution": "public", "path": "policies/base/repository-default.yaml", - "digest": "sha256:90d112ae49f4c44ebce3b749d543660548214061128926d3d6d11ba02827a414" + "digest": "sha256:5075e944793d13e6c506da09f136214872a40c5b91eabb06fae09d0993631649" }, { "id": "organization-default", @@ -50,26 +50,17 @@ "github": { "actions": { "allowed_actions": { - "management": "managed", - "value": "selected" + "management": "observed" }, "enabled": { "management": "managed", "value": true }, "selected_actions": { - "management": "managed", - "value": { - "github_owned_allowed": true, - "patterns_allowed": [ - "NDDev-it-com/nddev-ci-workflows/.github/workflows/*@*" - ], - "verified_allowed": false - } + "management": "observed" }, "sha_pinning_required": { - "management": "managed", - "value": true + "management": "observed" } }, "merge": { @@ -198,13 +189,6 @@ "file": "policies/base/repository-default.yaml", "operation": "set" }, - "/effective/github/actions/allowed_actions/value": { - "source": "repository-default", - "tier": "base", - "priority": 100, - "file": "policies/base/repository-default.yaml", - "operation": "set" - }, "/effective/github/actions/enabled/management": { "source": "repository-default", "tier": "base", @@ -226,27 +210,6 @@ "file": "policies/base/repository-default.yaml", "operation": "set" }, - "/effective/github/actions/selected_actions/value/github_owned_allowed": { - "source": "repository-default", - "tier": "base", - "priority": 100, - "file": "policies/base/repository-default.yaml", - "operation": "set" - }, - "/effective/github/actions/selected_actions/value/patterns_allowed/0": { - "source": "repository-default", - "tier": "base", - "priority": 100, - "file": "policies/base/repository-default.yaml", - "operation": "set" - }, - "/effective/github/actions/selected_actions/value/verified_allowed": { - "source": "repository-default", - "tier": "base", - "priority": 100, - "file": "policies/base/repository-default.yaml", - "operation": "set" - }, "/effective/github/actions/sha_pinning_required/management": { "source": "repository-default", "tier": "base", @@ -254,13 +217,6 @@ "file": "policies/base/repository-default.yaml", "operation": "set" }, - "/effective/github/actions/sha_pinning_required/value": { - "source": "repository-default", - "tier": "base", - "priority": 100, - "file": "policies/base/repository-default.yaml", - "operation": "set" - }, "/effective/github/merge/allow_auto_merge/management": { "source": "repository-default", "tier": "base", diff --git a/AGENTS.md b/AGENTS.md index ca0f2c1..9faf01f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,9 +2,9 @@ GENERATED FILE - DO NOT EDIT DIRECTLY generator: gds bundle: 0.1.0-dev -source-commit: 06dd4355b03d600bbf40eee21e86fb9e424d42c5 -input-digest: sha256:229eda43354f1690ccec8d8070eb8392b3072c1ab9ff80b451ce500a5d8867cb -output-digest: sha256:e88382bea884b0476e6a65f5568a98aec71c43dc0544eecd2c2d882843fc6b18 +source-commit: 7120d79f77124632c6cf355bca27598c120c1182 +input-digest: sha256:24879f858628a87b9316a2f7a91d090c53b39723491abc207f8dec44d4eaab95 +output-digest: sha256:e105b92a1cbdf280afc3902b666ffc347b1fa864a7644b85ffe5796d1de5c7b9 edit-source: - .gds/repository.yaml - policies/base/repository-default.yaml @@ -40,7 +40,7 @@ edit-source: ## Development -- No repository-owned verification command is declared; report it as `NOT_PROVEN`. +- Test: `python3 cli-tools/validate_public_contracts.py`. ## Agent routing From eb8fd9515326064080f9e28bc49fcf8c4a25401c Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Tue, 14 Jul 2026 09:47:49 +0700 Subject: [PATCH 3/5] chore(release): reset evidence to execution-bound schema 2 pending bundle --- build/release-evidence.json | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/build/release-evidence.json b/build/release-evidence.json index 9471995..da88777 100644 --- a/build/release-evidence.json +++ b/build/release-evidence.json @@ -1,19 +1,19 @@ { - "schema_version": 1, + "schema_version": 2, "module": { "repository": "NDDev-it-com/nddev-zcode-app", - "setup_digest": "sha256:e99bb94d6fdf28c9e7c8aa998414cbbfab882c8a7a27194c0536502995380738" + "setup_digest": "sha256:63fd889d459afa3402352749f8fea096fb99fa42fd016fadf3f2bff75324c63c" }, "harness": { "repository": "NDDev-it-com/nddev-harnesses", - "commit": "2b1843fc5646829857006e44a74e96768dfe053e" + "commit": "4a87355de2bf4019fcd106a73778c72ab99fac50" }, "adapter": { "id": "zcode", - "version": "2.1.2" + "version": "2.1.3" }, "vendor": { - "app_version": "3.3.4", + "app_version": "3.3.5", "cli_version": "0.15.2", "runtime_model": "GLM-5.2" }, @@ -29,17 +29,11 @@ "nddev_support_tier": "supported" } ], - "checks": [ - "fast", - "release", - "platform:macos", - "platform:ubuntu", - "benchmark:macos", - "benchmark:ubuntu" - ], - "generated_at_utc": "2026-07-13T14:36:48Z", - "expires_at_utc": "2027-01-09T14:36:48Z", + "lanes": [], + "generated_at_utc": "2026-07-14T02:47:49Z", + "expires_at_utc": "2027-01-10T02:47:49Z", "promotion": { - "decision": "approved" + "decision": "pending", + "waivers": [] } } From cec7fb3182131edc8e1f7ba517c7364eb628984f Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Tue, 14 Jul 2026 09:49:35 +0700 Subject: [PATCH 4/5] feat(release): require execution-bound schema-2 evidence at tag time --- .github/workflows/release.yml | 86 +++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b62a52..508337b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -147,24 +147,42 @@ jobs: import sys EVIDENCE_PATH = "build/release-evidence.json" + REQUIRED_LANES = ( + "fast", + "release", + "platform:macos", + "platform:ubuntu", + "benchmark:macos", + "benchmark:ubuntu", + ) + SHA256 = re.compile(r"sha256:[0-9a-f]{64}") def fail(message): print(f"::error::{message}", file=sys.stderr) raise SystemExit(1) + def parse_utc(value): + if not isinstance(value, str) or not value.endswith("Z"): + return None + try: + parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return None + return parsed if parsed.utcoffset() == dt.timedelta(0) else None + try: evidence = json.loads(pathlib.Path(EVIDENCE_PATH).read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as exc: fail(f"cannot read {EVIDENCE_PATH}: {exc}") if not isinstance(evidence, dict): fail("release-evidence must be a JSON object") - if evidence.get("schema_version") != 1: - fail("release-evidence schema_version must be 1") + if evidence.get("schema_version") != 2: + fail("release-evidence schema_version must be 2 (execution-bound)") module = evidence.get("module") if not isinstance(module, dict) or module.get("repository") != "NDDev-it-com/nddev-zcode-app": fail("release-evidence.module.repository must be this repository") digest = module.get("setup_digest") - if not isinstance(digest, str) or re.fullmatch(r"sha256:[0-9a-f]{64}", digest) is None: + if not isinstance(digest, str) or SHA256.fullmatch(digest) is None: fail("release-evidence.module.setup_digest must be a sha256 content digest") # Bind the recorded digest to the exact tagged content: hash every @@ -201,19 +219,53 @@ jobs: platforms = evidence.get("platforms") if not isinstance(platforms, list) or not platforms: fail("release-evidence.platforms must be a non-empty array") - checks = evidence.get("checks") - if not isinstance(checks, list) or not checks or not all(isinstance(item, str) and item for item in checks): - fail("release-evidence.checks must be a non-empty array of strings") - # Freshness: the bundle must be inside its validated window at tag time. - def parse_utc(value): - if not isinstance(value, str) or not value.endswith("Z"): - return None - try: - parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00")) - except ValueError: - return None - return parsed if parsed.utcoffset() == dt.timedelta(0) else None + promotion = evidence.get("promotion") + if not isinstance(promotion, dict) or promotion.get("decision") != "approved": + fail("release-evidence.promotion.decision must be approved; pending bundles cannot release") + approved_by = promotion.get("approved_by") + if not isinstance(approved_by, dict) or not str(approved_by.get("mechanism", "")).strip() or not str(approved_by.get("identity", "")).strip(): + fail("release-evidence.promotion.approved_by must record mechanism and identity") + + lanes = evidence.get("lanes") + if not isinstance(lanes, list): + fail("release-evidence.lanes must be an array of execution records") + seen = {} + for index, record in enumerate(lanes): + context = f"release-evidence.lanes[{index}]" + if not isinstance(record, dict): + fail(f"{context} must be an object") + if record.get("status") != "passed": + fail(f"{context}: status must be passed") + lane = str(record.get("lane", "")) + seen[lane] = seen.get(lane, 0) + 1 + for key in ("workflow", "run_id", "runner", "toolchain", "platform"): + if not str(record.get(key, "")).strip(): + fail(f"{context}: {key} must be a non-empty string") + if record.get("setup_digest") != digest: + fail(f"{context}: setup_digest does not match the bundle module digest") + if not isinstance(record.get("log_digest"), str) or SHA256.fullmatch(record["log_digest"]) is None: + fail(f"{context}: log_digest must be a sha256 digest") + started = parse_utc(record.get("started_at_utc")) + completed = parse_utc(record.get("completed_at_utc")) + if started is None or completed is None or started > completed: + fail(f"{context}: execution timestamps are missing or inverted") + for lane in REQUIRED_LANES: + if seen.get(lane, 0) != 1: + fail(f"release-evidence: lane {lane} must have exactly one passed execution record") + if set(seen) - set(REQUIRED_LANES): + fail("release-evidence: unknown lane records present") + + observation = evidence.get("vendor_observation") + if not isinstance(observation, dict): + fail("release-evidence.vendor_observation must record the currentness check") + for key in ("source", "observed_latest_version", "event_date"): + if not str(observation.get(key, "")).strip(): + fail(f"release-evidence.vendor_observation.{key} must be a non-empty string") + if parse_utc(observation.get("observed_at_utc")) is None: + fail("release-evidence.vendor_observation.observed_at_utc must be ISO-8601 UTC") + if not isinstance(observation.get("artifact_evidence_digest"), str) or SHA256.fullmatch(observation["artifact_evidence_digest"]) is None: + fail("release-evidence.vendor_observation.artifact_evidence_digest must be a sha256 digest") generated_at = parse_utc(evidence.get("generated_at_utc")) expires_at = parse_utc(evidence.get("expires_at_utc")) @@ -227,9 +279,7 @@ jobs: if now < generated_at: fail("release-evidence.generated_at_utc is in the future") - if evidence.get("promotion", {}).get("decision") != "approved": - fail("release-evidence.promotion.decision must be approved") - print(f"ok: release-evidence bundle recomputed and consistent for {version_file}") + print(f"ok: execution-bound release-evidence verified for {version_file}") BUNDLE publish: From 3dd1e050f0d85d2fd1fc625a130a84d4ef17e8fb Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Tue, 14 Jul 2026 09:50:29 +0700 Subject: [PATCH 5/5] chore(release): rebind pending evidence to final module content digest --- build/release-evidence.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/release-evidence.json b/build/release-evidence.json index da88777..ceb5096 100644 --- a/build/release-evidence.json +++ b/build/release-evidence.json @@ -2,7 +2,7 @@ "schema_version": 2, "module": { "repository": "NDDev-it-com/nddev-zcode-app", - "setup_digest": "sha256:63fd889d459afa3402352749f8fea096fb99fa42fd016fadf3f2bff75324c63c" + "setup_digest": "sha256:b4d27878708e72471aa090a3ca7c72d2b1dca4db9309159838e089d18c15748c" }, "harness": { "repository": "NDDev-it-com/nddev-harnesses", @@ -30,8 +30,8 @@ } ], "lanes": [], - "generated_at_utc": "2026-07-14T02:47:49Z", - "expires_at_utc": "2027-01-10T02:47:49Z", + "generated_at_utc": "2026-07-14T02:50:28Z", + "expires_at_utc": "2027-01-10T02:50:28Z", "promotion": { "decision": "pending", "waivers": []