Skip to content

chore: realign module version to 0.1.0 pre-release baseline #2

chore: realign module version to 0.1.0 pre-release baseline

chore: realign module version to 0.1.0 pre-release baseline #2

Workflow file for this run

name: release
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
permissions: {}
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
verify:
name: Verify release contract
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: block
allowed-endpoints: >
agent.api.stepsecurity.io:443
api.github.com:443
github.com:443
codeload.github.com:443
objects.githubusercontent.com:443
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- name: Verify public contracts
env:
RELEASE_TAG: ${{ github.ref_name }}
RELEASE_SHA: ${{ github.sha }}
run: |
set -euo pipefail
python3 -I - <<'PY'
import json
import os
import pathlib
import py_compile
import re
import runpy
semver = re.compile(
r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)"
r"(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?"
r"(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?"
)
version = pathlib.Path("VERSION").read_text(encoding="ascii").strip()
version_data = json.loads(pathlib.Path("build/version.json").read_text())
manifest = json.loads(pathlib.Path("build/manifest.json").read_text())
contract = json.loads(pathlib.Path("config/nddev-contract.json").read_text())
baseline = json.loads(
pathlib.Path("references/codex-baseline.json").read_text()
)
versions = {
os.environ["RELEASE_TAG"],
version,
version_data["build_version"],
manifest["build_version"],
}
if len(versions) != 1 or semver.fullmatch(version) is None:
raise SystemExit(f"release version mismatch: {sorted(versions)}")
if version_data["schema_version"] != 2:
raise SystemExit("unsupported build version schema")
if manifest["schema_version"] != 3:
raise SystemExit("unsupported manifest schema")
if contract["contract_version"] != 3:
raise SystemExit("unsupported public contract version")
if baseline["schema_version"] != 2:
raise SystemExit("unsupported runtime baseline schema")
permission_profiles_since = version_data[
"codex_permission_profiles_since"
]
tested = version_data["codex_cli_tested"]
if semver.fullmatch(permission_profiles_since) is None:
raise SystemExit(
"invalid Codex permission-profile compatibility floor: "
f"{permission_profiles_since}"
)
if semver.fullmatch(tested) is None:
raise SystemExit(f"invalid Codex tested version: {tested}")
minimum_tuple = tuple(
int(part) for part in permission_profiles_since.split(".")
)
tested_tuple = tuple(int(part) for part in tested.split("."))
if tested_tuple < minimum_tuple:
raise SystemExit(
"Codex tested version is below the permission-profile floor"
)
baseline_ref = "references/codex-baseline.json"
if version_data["runtime_baseline_ref"] != baseline_ref:
raise SystemExit("build version baseline reference mismatch")
if manifest["runtime_compatibility"]["baseline_ref"] != baseline_ref:
raise SystemExit("manifest baseline reference mismatch")
if contract["runtime_compatibility"]["baseline_ref"] != baseline_ref:
raise SystemExit("contract baseline reference mismatch")
expected_version_refs = {
"tested_version_ref": "build/version.json:codex_cli_tested"
}
if baseline["codex_cli"] != expected_version_refs:
raise SystemExit("Codex baseline version references mismatch")
if baseline["permission_profiles"] != {
"configuration_key": "default_permissions",
"custom_profiles_surface": "[permissions.<name>]",
"custom_profiles_used": False,
"managed_enforcement": False,
"minimum_codex_version_ref": (
"build/version.json:codex_permission_profiles_since"
),
"status": "beta",
}:
raise SystemExit("Codex permission-profile baseline mismatch")
if baseline["evidence"]["official_permissions_documentation"] != (
"https://learn.chatgpt.com/docs/permissions"
):
raise SystemExit("Codex baseline documentation mismatch")
if baseline["evidence"]["official_agents_documentation"] != (
"https://learn.chatgpt.com/docs/agent-configuration/agents-md"
"#how-codex-discovers-guidance"
):
raise SystemExit("Codex instruction baseline documentation mismatch")
expected_permission_defaults = {
"admin_enforcement": False,
"built_in_profiles_only": True,
"configuration_precedence_applies": True,
"custom_permissions_table_used": False,
}
if contract["permission_defaults"] != expected_permission_defaults:
raise SystemExit("public permission-default contract mismatch")
transaction = manifest["transaction_policy"]
if {
"agents_override_blocked_commands": transaction[
"agents_override_blocked_commands"
],
"managed_file_mode": transaction["managed_file_mode"],
"new_target_mode": transaction["new_target_mode"],
"preserve_existing_target_mode": transaction[
"preserve_existing_target_mode"
],
"reject_agents_override": transaction["reject_agents_override"],
} != {
"agents_override_blocked_commands": [
"plan",
"apply",
"switch",
"restore",
"launch",
],
"managed_file_mode": "0600",
"new_target_mode": "0700",
"preserve_existing_target_mode": True,
"reject_agents_override": True,
}:
raise SystemExit("manifest lifecycle hardening contract mismatch")
expected_launch = {
"command": "<absolute-target>/bin/codex",
"direct_command": (
"CODEX_HOME=<absolute-target> "
"<absolute-target>/bin/codex [args...]"
),
"environment_variable": "CODEX_HOME",
"executable_source": "validated-target-owned-standalone-install",
"forwards_arguments": True,
"forwards_child_exit_code": True,
"json_manager_errors_supported": True,
"manager_command": (
"python3 cli-tools/nddev_codex.py launch "
"--target <absolute-target> -- [args...]"
),
"requires_clean_managed_target": True,
"required_version": tested,
"streams_child_stdio": True,
"successful_child_output_wrapped": False,
"target_environment_scope": "child-process-only",
"target_must_equal_runtime_home": True,
}
if manifest["runtime_launch"] != expected_launch:
raise SystemExit("manifest launch contract mismatch")
if contract["runtime_launch"] != expected_launch:
raise SystemExit("public launch contract mismatch")
if manifest["software_install"] != contract["software_install"]:
raise SystemExit("software-install contract mismatch")
software = contract["software_install"]
if software["tested_version"] != tested:
raise SystemExit("software-install version mismatch")
if software["installer"]["release_tag"] != f"rust-v{tested}":
raise SystemExit("software-install release tag mismatch")
if software["installer"]["abnormal_exit_cleanup"] != (
"terminate-process-group-before-unlock"
):
raise SystemExit("software-install cleanup contract mismatch")
if software["desktop"] != {
"command": (
"python3 cli-tools/nddev_codex.py desktop --target "
"<absolute-target> [--workspace <absolute-dir>]"
),
"configuration_binding": "not-guaranteed-by-codex-app",
"delegate": "codex app",
"documented_product": "ChatGPT",
"direct_app_management": False,
"pinned_cli_packaging": "Codex.app/Codex.dmg",
"requires_managed_setup": False,
"supported_platforms": ["macos"],
}:
raise SystemExit("desktop bridge contract mismatch")
expected = {
"safe": (":read-only", "on-request"),
"full-auto": (":danger-full-access", "never"),
}
parse_permission_config = runpy.run_path(
"cli-tools/nddev_codex.py"
)["parse_permission_config"]
for setup_id, values in expected.items():
path = pathlib.Path(f"setups/{setup_id}/config.toml")
config = parse_permission_config(
path.read_text(encoding="utf-8"), str(path)
)
actual = (config["default_permissions"], config["approval_policy"])
if actual != values:
raise SystemExit(f"invalid permission contract for {setup_id}")
py_compile.compile("cli-tools/nddev_codex.py", doraise=True)
PY
git fetch --no-tags --force origin \
'+refs/heads/main:refs/remotes/origin/main'
tag_commit="$(git rev-parse --verify "${RELEASE_SHA}^{commit}")"
git merge-base --is-ancestor "$tag_commit" refs/remotes/origin/main
- name: Verify release-evidence bundle
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"
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") != 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-codex-app":
fail("release-evidence.module.repository must be this repository")
digest = module.get("setup_digest")
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
# 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")
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"))
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")
print(f"ok: execution-bound release-evidence verified for {version_file}")
BUNDLE
publish:
name: Publish attested immutable release
needs: verify
permissions:
contents: write # Create the release and upload its immutable assets.
id-token: write # Authenticate keyless signing through GitHub OIDC.
attestations: write # Publish build provenance for the release assets.
artifact-metadata: write # Record artifact metadata for the published release.
uses: NDDev-it-com/ci-workflows/.github/workflows/release-supply-chain.yml@2ccb80e96f5771b6a6b4eae63a4f47e232906dc7 # 0.12.0
with:
version: ${{ github.ref_name }}
package_name: nddev-codex-app
archive_paths: >-
README.md LICENSE VERSION CHANGELOG.md SECURITY.md .gitignore .agents
.github build cli-tools config plugins references setups
runtime_paths: >-
README.md LICENSE VERSION .agents build cli-tools config plugins
references setups