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
2 changes: 1 addition & 1 deletion .github/workflows/actionlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ jobs:
name: actionlint
permissions:
contents: read
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/actionlint.yml@eda8ff7eb970c05a219238b6e5fb7faad8f67c40 # 0.7.0
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/actionlint.yml@8b8e3ea6321c912b68eec831c2072a0173203433 # 0.8.1
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
contents: read
security-events: write # Publish CodeQL analysis to code scanning.
actions: read # Read workflow metadata for the actions language.
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/public-codeql.yml@eda8ff7eb970c05a219238b6e5fb7faad8f67c40 # 0.7.0
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/public-codeql.yml@8b8e3ea6321c912b68eec831c2072a0173203433 # 0.8.1
with:
languages: '["actions"]'
queries: security-and-quality
2 changes: 1 addition & 1 deletion .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ jobs:
permissions:
contents: read
pull-requests: write # Comment when dependency policy rejects a change.
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/public-dependency-review.yml@eda8ff7eb970c05a219238b6e5fb7faad8f67c40 # 0.7.0
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/public-dependency-review.yml@8b8e3ea6321c912b68eec831c2072a0173203433 # 0.8.1
70 changes: 64 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,24 @@ jobs:
run: |
set -euo pipefail
python3 -I <<'BUNDLE'
import datetime as dt
import hashlib
import json
import pathlib
import re
import subprocess
import sys

EVIDENCE_PATH = "build/release-evidence.json"

def fail(message):
print(f"::error::{message}", file=sys.stderr)
raise SystemExit(1)

try:
evidence = json.loads(
pathlib.Path("build/release-evidence.json").read_text(encoding="utf-8")
)
evidence = json.loads(pathlib.Path(EVIDENCE_PATH).read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
fail(f"cannot read build/release-evidence.json: {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:
Expand All @@ -160,16 +163,70 @@ jobs:
digest = module.get("setup_digest")
if not isinstance(digest, str) or re.fullmatch(r"sha256:[0-9a-f]{64}", 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
# tracked blob by path and id, excluding the evidence file itself
# (identical to scripts/evidence.compute_setup_digest in the harness).
listing = subprocess.run(
["git", "ls-files", "-s"], capture_output=True, text=True, check=True
).stdout
entries = []
for line in listing.splitlines():
metadata, _, path = line.partition("\t")
fields = metadata.split()
if not path or len(fields) != 3:
fail(f"unexpected git ls-files entry: {line!r}")
if path == EVIDENCE_PATH:
continue
entries.append(f"{path} {fields[1]}")
recomputed = "sha256:" + hashlib.sha256("\n".join(sorted(entries)).encode("utf-8")).hexdigest()
if digest != recomputed:
fail(
"release-evidence.module.setup_digest does not match the tagged content "
f"(recorded {digest}, recomputed {recomputed})"
)

harness = evidence.get("harness")
if not isinstance(harness, dict) or re.fullmatch(r"[0-9a-f]{40}", str(harness.get("commit"))) is None:
fail("release-evidence.harness.commit must be a full harness commit SHA")
adapter = evidence.get("adapter")
version_file = pathlib.Path("VERSION").read_text(encoding="utf-8").strip()
if not isinstance(adapter, dict) or adapter.get("version") != version_file:
fail("release-evidence.adapter.version must equal VERSION")
if not isinstance(evidence.get("vendor"), dict) or not evidence["vendor"]:
fail("release-evidence.vendor must be a non-empty object")
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

generated_at = parse_utc(evidence.get("generated_at_utc"))
expires_at = parse_utc(evidence.get("expires_at_utc"))
if generated_at is None or expires_at is None:
fail("release-evidence timestamps must be ISO-8601 UTC ending in Z")
now = dt.datetime.now(dt.timezone.utc)
if generated_at >= expires_at:
fail("release-evidence.generated_at_utc must precede expires_at_utc")
if now >= expires_at:
fail("release-evidence has expired; regenerate after re-validation")
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 present and consistent for {version_file}")
print(f"ok: release-evidence bundle recomputed and consistent for {version_file}")
BUNDLE

publish:
Expand All @@ -179,7 +236,8 @@ jobs:
contents: write # Create the immutable release and its exact assets.
id-token: write # Exchange workflow identity for attestations.
attestations: write # Publish provenance and SBOM attestations.
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/release-supply-chain.yml@eda8ff7eb970c05a219238b6e5fb7faad8f67c40 # 0.7.0
artifact-metadata: write # Record the actions/attest artifact storage entry (required at v4.1.1).
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/release-supply-chain.yml@8b8e3ea6321c912b68eec831c2072a0173203433 # 0.8.1
with:
version: ${{ github.ref_name }}
package_name: nddev-zcode-app
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ jobs:
contents: read
actions: read # Inspect workflow metadata for Scorecard checks.
id-token: write # Obtain OIDC identity for the Scorecard artifact.
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/public-scorecard-json.yml@eda8ff7eb970c05a219238b6e5fb7faad8f67c40 # 0.7.0
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/public-scorecard-json.yml@8b8e3ea6321c912b68eec831c2072a0173203433 # 0.8.1
2 changes: 1 addition & 1 deletion .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ concurrency:
jobs:
secret-scan:
name: Secret scan
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/secret-scan.yml@eda8ff7eb970c05a219238b6e5fb7faad8f67c40 # 0.7.0
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/secret-scan.yml@8b8e3ea6321c912b68eec831c2072a0173203433 # 0.8.1
2 changes: 1 addition & 1 deletion .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
permissions:
contents: read
security-events: write # Publish workflow-analysis results to code scanning.
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/zizmor-sarif.yml@eda8ff7eb970c05a219238b6e5fb7faad8f67c40 # 0.7.0
uses: NDDev-it-com/nddev-ci-workflows/.github/workflows/zizmor-sarif.yml@8b8e3ea6321c912b68eec831c2072a0173203433 # 0.8.1
with:
persona: pedantic
min_severity: low
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": 1,
"module": {
"repository": "NDDev-it-com/nddev-zcode-app",
"setup_digest": "sha256:024e07675c09c94849000f18275727a6e4a1624be663264021aa7025ba64f08b"
"setup_digest": "sha256:1868c8955f0656cf02522ee177d04c53f08aa2a8d6cdb62d7b1610bcfe9ce54c"
},
"harness": {
"repository": "NDDev-it-com/nddev-harnesses",
"commit": "d432aae15a3a3fe48dab5cb447ccc03abe5c78f8"
"commit": "e17e5f403914a248a3b7d53a251ee5136d94f449"
},
"adapter": {
"id": "zcode",
Expand Down Expand Up @@ -37,8 +37,8 @@
"benchmark:macos",
"benchmark:ubuntu"
],
"generated_at_utc": "2026-07-11T19:33:20Z",
"expires_at_utc": "2027-01-07T19:33:20Z",
"generated_at_utc": "2026-07-11T22:04:47Z",
"expires_at_utc": "2027-01-07T22:04:47Z",
"promotion": {
"decision": "approved"
}
Expand Down
Loading