diff --git a/build.sh b/build.sh index 57b6d5b4..079a96f1 100755 --- a/build.sh +++ b/build.sh @@ -102,14 +102,7 @@ if [ -n "${SSH_PUBLIC_KEY:-}" ]; then fi if [ "${BUILD_CHANNEL}" = "release" ]; then - case "${CLAWDIE_REF}" in - v[0-9]*.[0-9]*.[0-9]*) ;; - *) - echo "ERROR: release builds must pin a Clawdie-AI tag with --clawdie-version X.Y.Z" - echo " Current Clawdie ref: ${CLAWDIE_REF}" - exit 1 - ;; - esac + check_release_gate fi # The ISO carries its own product version (ISO_VERSION in build.cfg). It does not @@ -467,6 +460,69 @@ is_pinned_clawdie_ref() { printf '%s' "$_ref" | grep -Eq '^[0-9a-fA-F]{40}$|^v[0-9]+\.[0-9]+\.[0-9]+$' } +# Release builds require clean, committed sources across the whole stack. +# A release ISO must be a reproducible artifact — no uncommitted local patches. +check_release_gate() { + case "${CLAWDIE_REF}" in + v[0-9]*.[0-9]*.[0-9]*) ;; + *) + echo "ERROR: release builds must pin a Clawdie-AI tag with --clawdie-version X.Y.Z" + echo " Current Clawdie ref: ${CLAWDIE_REF}" + exit 1 + ;; + esac + + _release_errors=0 + + # Clawdie-AI local checkout + resolve_clawdie_ai_repo + if command -v git >/dev/null 2>&1 && git -C "${_resolved_clawdie_ai_repo}" rev-parse --git-dir >/dev/null 2>&1; then + if ! git -C "${_resolved_clawdie_ai_repo}" diff --quiet 2>/dev/null || ! git -C "${_resolved_clawdie_ai_repo}" diff --cached --quiet 2>/dev/null; then + echo "ERROR: release builds require a clean clawdie-ai repo" + echo " Uncommitted changes detected in: ${_resolved_clawdie_ai_repo}" + _release_errors=$((_release_errors + 1)) + fi + fi + + # ISO repo itself + if command -v git >/dev/null 2>&1 && git -C "${SCRIPT_DIR}" rev-parse --git-dir >/dev/null 2>&1; then + if ! git -C "${SCRIPT_DIR}" diff --quiet 2>/dev/null || ! git -C "${SCRIPT_DIR}" diff --cached --quiet 2>/dev/null; then + echo "ERROR: release builds require a clean clawdie-iso repo" + echo " Uncommitted changes detected in: ${SCRIPT_DIR}" + _release_errors=$((_release_errors + 1)) + fi + fi + + # Colibri + if [ "${FEATURE_COLIBRI:-NO}" = "YES" ]; then + resolve_colibri_paths + if command -v git >/dev/null 2>&1 && git -C "${_resolved_colibri_repo}" rev-parse --git-dir >/dev/null 2>&1; then + if ! git -C "${_resolved_colibri_repo}" diff --quiet 2>/dev/null || ! git -C "${_resolved_colibri_repo}" diff --cached --quiet 2>/dev/null; then + echo "ERROR: release builds require a clean colibri repo" + echo " Uncommitted changes detected in: ${_resolved_colibri_repo}" + _release_errors=$((_release_errors + 1)) + fi + fi + + # Zot (agent binary) + if [ "${COLIBRI_STAGE_AGENT:-YES}" = "YES" ]; then + resolve_zot_paths + if command -v git >/dev/null 2>&1 && git -C "${_resolved_zot_repo}" rev-parse --git-dir >/dev/null 2>&1; then + if ! git -C "${_resolved_zot_repo}" diff --quiet 2>/dev/null || ! git -C "${_resolved_zot_repo}" diff --cached --quiet 2>/dev/null; then + echo "ERROR: release builds require a clean zot repo" + echo " Uncommitted changes detected in: ${_resolved_zot_repo}" + _release_errors=$((_release_errors + 1)) + fi + fi + fi + fi + + if [ "${_release_errors}" -gt 0 ]; then + echo "ERROR: release build aborted — ${_release_errors} dirty repo(s). Use BUILD_CHANNEL=dev for iteration builds." + exit 1 + fi +} + write_build_manifest() { _manifest_path="$1" _iso_repo_commit="unknown" @@ -500,6 +556,19 @@ write_build_manifest() { fi fi fi + # Zot provenance (agent binary built from source — record if repo is dirty). + _zot_dirty="null" + if [ "${FEATURE_COLIBRI:-NO}" = "YES" ] && [ "${COLIBRI_STAGE_AGENT:-YES}" = "YES" ]; then + resolve_zot_paths + if command -v git >/dev/null 2>&1 && git -C "${_resolved_zot_repo}" rev-parse --git-dir >/dev/null 2>&1; then + if git -C "${_resolved_zot_repo}" diff --quiet 2>/dev/null && \ + git -C "${_resolved_zot_repo}" diff --cached --quiet 2>/dev/null; then + _zot_dirty="false" + else + _zot_dirty="true" + fi + fi + fi mkdir -p "$(dirname "$_manifest_path")" cat > "$_manifest_path" <