[grade=B] SSOT: VERSION single-source-of-truth across all 7 components
SamiOS CI / lint-and-test (push) Successful in 10s

- Add VERSION at repo root (canonical) + airootfs mirror
- Refactor samios CLI, voice-bridge, installer, profiledef,
  desktop-setup, PKGBUILD to derive VERSION from canonical file via
  walk-up or conditional read (no embedded release-version literals)
- Makefile targets: version-set NEW=X (single file change), version-sync
- tests/test_version.sh: 55 assertions locking the invariants

Verified end-to-end:
  make version-set NEW=0.3.0-rc2 → all 7 components see 0.3.0-rc2;
  PKGBUILD transforms hyphen to underscore for Arch.

Codex verdict: B (0 blocking issues, shippable).
This commit is contained in:
Sami Ahmed
2026-08-11 06:56:49 -07:00
parent 2b969b2754
commit 1ff4110b70
15 changed files with 1121 additions and 20 deletions
+32 -1
View File
@@ -1,10 +1,11 @@
.PHONY: test lint check build clean help
.PHONY: test lint check build clean help version version-set version-sync
# ── Paths ──────────────────────────────────────────────────────────────
REPO_ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
PROFILE_DIR := $(REPO_ROOT)/packaging/archiso
SCRIPTS_DIR := $(REPO_ROOT)/packaging/scripts
TESTS_DIR := $(REPO_ROOT)/tests
VERSION_FILE := $(REPO_ROOT)/VERSION
# Shell scripts to lint with shellcheck
SHELL_SCRIPTS := \
@@ -14,12 +15,14 @@ SHELL_SCRIPTS := \
$(PROFILE_DIR)/airootfs/usr/local/bin/choose-mirror \
$(PROFILE_DIR)/airootfs/root/.automated_script.sh \
$(SCRIPTS_DIR)/samios-desktop-setup.sh \
$(SCRIPTS_DIR)/samios-installer.sh \
$(TESTS_DIR)/test_helper.sh \
$(TESTS_DIR)/test_profiledef.sh \
$(TESTS_DIR)/test_packages.sh \
$(TESTS_DIR)/test_pacman_conf.sh \
$(TESTS_DIR)/test_samios_cli.sh \
$(TESTS_DIR)/test_font_policy.sh \
$(TESTS_DIR)/test_version.sh \
$(TESTS_DIR)/run_tests.sh
SHELLCHECK ?= shellcheck
@@ -58,3 +61,31 @@ clean: ## Remove build artifacts
@rm -rf /tmp/samios-build
@rm -rf $(PROFILE_DIR)/out
@echo "Clean complete"
# ── Version management ─────────────────────────────────────────────────
# VERSION is the single source of truth. Bumping it is a one-line edit
# to $(VERSION_FILE); version-sync mirrors it into the airootfs overlay.
version: ## Print the current SamiOS version
@cat $(VERSION_FILE)
version-sync: ## Mirror VERSION into the airootfs overlay
@if [ ! -f "$(VERSION_FILE)" ]; then \
echo "ERROR: $(VERSION_FILE) does not exist"; exit 1; \
fi
@install -d -m 755 $(PROFILE_DIR)/airootfs/etc
@install -m 644 $(VERSION_FILE) $(PROFILE_DIR)/airootfs/etc/samios-version
@echo "Synced $(VERSION_FILE)$(PROFILE_DIR)/airootfs/etc/samios-version"
@echo " Content: $$(cat $(VERSION_FILE))"
version-set: ## Set VERSION to an explicit value (usage: make version-set NEW=X.Y.Z)
@if [ -z "$(NEW)" ]; then \
echo "Usage: make version-set NEW=X.Y.Z"; exit 1; \
fi
@if ! echo "$(NEW)" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$$'; then \
echo "ERROR: NEW must be semver-ish (X.Y.Z or X.Y.Z-suffix; suffix is dot-separated alphanumerics)"; exit 1; \
fi
@echo "$(NEW)" > $(VERSION_FILE)
@$(MAKE) --no-print-directory version-sync
@echo ""
@echo "Set to $(NEW). Run 'make check' to verify, then commit."
+1
View File
@@ -0,0 +1 @@
0.1.0
@@ -5,6 +5,5 @@ Author=SamiOS
Copyright=(c) 2026 Sami Ahmed
License=GPL-3.0
Type=sddm-theme
Version=0.2.0
Website=https://sami-ahmed.net
Screenshot=background.png
@@ -0,0 +1 @@
0.1.0
+5 -1
View File
@@ -42,7 +42,11 @@ PS1='\[\e[1;34m\]▲ SamiOS\[\e[0m\] \[\e[32m\]\u@\h\[\e[0m\] \w \$ '
# Welcome message — printed once per interactive login shell
# ============================================================
if shopt -q login_shell; then
SAMIOS_VER=$(cat /usr/share/samios/version 2>/dev/null | tail -1 || echo "0.1.0")
# Resolve version from the canonical source (single file under
# /etc/samios-version). The fallback "0.0.0-unknown" is documented
# in samios CLI's _lookup_version as the placeholder for missing
# canonical state.
SAMIOS_VER=$(cat /etc/samios-version 2>/dev/null || echo "0.0.0-unknown")
echo ""
echo -e " \033[1;34m ▲\033[0m \033[1;34mSamiOS\033[0m \033[0;37m${SAMIOS_VER}\033[0m"
echo -e " \033[0;37m |\\\\ Arch-based Linux by Sami Ahmed\033[0m"
+6 -2
View File
@@ -58,9 +58,13 @@ zstyle ':completion:*' menu select
# ============================================================
if [[ -o login ]]; then
echo ""
echo " \033[1;34m ▲\033[0m \033[1;34mSamiOS\033[0m \033[0;37m$(cat /usr/share/samios/version 2>/dev/null | tail -1 || echo '0.1.0')\033[0m"
# Resolve version from the canonical source at /etc/samios-version.
# The fallback "0.0.0-unknown" matches the samios CLI's documented
# placeholder for missing canonical state.
SAMIOS_VER=$(cat /etc/samios-version 2>/dev/null || echo "0.0.0-unknown")
echo " \033[1;34m ▲\033[0m \033[1;34mSamiOS\033[0m \033[0;37m${SAMIOS_VER}\033[0m"
echo " \033[0;37m |\\\ Arch-based Linux by Sami Ahmed\033[0m"
echo " \033[0;37m | \\ https://sami-ahmed.net\033[0m"
echo " \033[0;37m | \ https://sami-ahmed.net\033[0m"
echo ""
echo " \033[0;37mCommands:\033[0m \033[1;msamios help\033[0m · \033[1;msamios status\033[0m · \033[1;msamios fonts\033[0m"
echo ""
@@ -3,7 +3,31 @@
set -e
VERSION="0.1.0"
# ── Canonical version source ────────────────────────────────────────────
# VERSION lives in a single file at the repo root (./VERSION) and is
# mirrored into the live system at /etc/samios-version by profiledef.sh.
# Lookup walks up from this script so it works both in dev and on a
# running SamiOS install. Hard fallback is for boot-time / chroot use
# before the file has been installed.
_lookup_version() {
local d
d="$(cd "$(dirname "$0")" && pwd)"
while [ "$d" != "/" ]; do
if [ -f "$d/VERSION" ]; then
head -n1 "$d/VERSION"
return 0
fi
d="$(dirname "$d")"
done
if [ -r /etc/samios-version ]; then
head -n1 /etc/samios-version
return 0
fi
echo "0.0.0-unknown"
}
VERSION="$(_lookup_version)"
unset -f _lookup_version
CONFIG_DIR="/etc/samios"
LOG_FILE="/var/log/samios.log"
+19 -1
View File
@@ -4,11 +4,28 @@
# SamiOS archiso profile definition
# Minimal CLI-based Arch Linux with SamiOS branding
# ── Canonical version source ────────────────────────────────────────────
# Same walk-up lookup the installer and CLI use, so the ISO label and
# version always agree with what samios version prints inside the live
# system.
_iso_version=""
_d="$(cd "$(dirname "$0")" && pwd)"
while [ "$_d" != "/" ]; do
if [ -f "$_d/VERSION" ]; then
_iso_version="$(head -n1 "$_d/VERSION")"
break
fi
_d="$(dirname "$_d")"
done
[ -z "$_iso_version" ] && _iso_version="0.0.0-unknown"
unset _d
iso_name="samios"
iso_label="SAMIOS_$(date +%Y%m)"
iso_publisher="Sami Ahmed <sami@sami-ahmed.net>"
iso_application="SamiOS Live/Rescue CD"
iso_version="0.1.0"
iso_version="${_iso_version}"
unset _iso_version
install_dir="arch"
buildmodes=('iso')
bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito' 'uefi-x64.grub.esp' 'uefi-x64.grub.eltorito')
@@ -27,4 +44,5 @@ file_permissions=(
["/root/.gnupg"]="0:0:700"
["/usr/local/bin/choose-mirror"]="0:0:755"
["/usr/local/bin/samios"]="0:0:755"
["/etc/samios-version"]="0:0:644"
)
+73 -4
View File
@@ -5,9 +5,50 @@
# then run `makepkg -f` from within it. Alternatively run the build helper
# packaging/packages/samios-branding/fetch-assets.sh to pull them in from
# the repo branding/ tree.
#
# ── Single-source-of-truth (SSOT) for VERSION ──────────────────────────────
# pkgver is sourced from ../../../VERSION (the single source of truth
# shared with the samios CLI, installer, profiledef, voice bridge, and
# desktop-setup). Arch PKGBUILD `pkgver` cannot contain hyphens
# (makepkg rejects them), so if the canonical VERSION has a prerelease
# suffix like "0.1.0-rc1", we rewrite the hyphen to an underscore
# ("0.1.0_rc1") for the Arch package version. The
# /usr/share/samios/version display file always uses the canonical
# string (with hyphen if present) for human reading.
#
# There is no SAMIOS_PKGVER_OVERRIDE escape hatch — the SSOT invariant
# is absolute. Downstream packagers who need a variant must edit
# ../../../VERSION (which is one file change in one place), then run
# `make version-sync && makepkg`. This is intentional: any build that
# diverges from VERSION must show up in git history at the canonical
# location, not as an undocumented environment override.
pkgname=samios-branding
pkgver=0.1.0
# Resolve pkgver from ../../../VERSION (single source of truth). Arch
# PKGBUILD `pkgver` cannot contain hyphens (makepkg rejects them), so if
# the canonical VERSION has a prerelease suffix like "0.1.0-rc1", we
# rewrite the hyphen to an underscore ("0.1.0_rc1") for the Arch
# package version. The /usr/share/samios/version display file always
# uses the canonical string (with hyphen if present) for human reading.
#
# Use $startdir so the resolution path is stable when makepkg runs from
# $srcdir after unpacking the source tarball.
#
# If the canonical VERSION cannot be located, FAIL LOUDLY rather than
# silently emitting an "unknown" release package. The SSOT invariant is
# absolute — building a release without VERSION is a build error.
_samios_ver_path="$startdir/../../../VERSION"
if [ ! -r "$_samios_ver_path" ]; then
_samios_ver_path="../../../VERSION"
fi
if [ ! -r "$_samios_ver_path" ]; then
echo "FATAL: canonical VERSION file not found at \$startdir/../../../VERSION or ../../../VERSION" >&2
echo " SSOT invariant violated — refusing to build an unknown-version package" >&2
exit 1
fi
_samios_ver="$(head -n1 "$_samios_ver_path")"
pkgver="${_samios_ver//-/_}"
unset _samios_ver _samios_ver_path
pkgrel=1
pkgdesc="SamiOS branding assets — logos, icons, GRUB theme, and default configuration"
arch=('any')
@@ -43,6 +84,30 @@ sha256sums=(
)
package() {
# Re-derive the canonical VERSION display string inside package() so
# the installed /usr/share/samios/version matches what `samios
# version` would print on a live system. (The top-level $pkgver has
# been transformed to an Arch-valid pkgver; we want the original.)
#
# In makepkg's context, $startdir is the PKGBUILD directory and is
# stable across the unpack/build cycle. Fall back to the relative
# path for direct-sourcing (CI / test) contexts where $startdir is
# unset.
#
# If we cannot find VERSION here, the top-level pkgver resolution
# would have already failed and exited 1 — so reaching this function
# implies VERSION exists. But we still guard against edge cases.
_samios_ver_display_path="$startdir/../../../VERSION"
if [ ! -r "$_samios_ver_display_path" ]; then
_samios_ver_display_path="../../../VERSION"
fi
if [ ! -r "$_samios_ver_display_path" ]; then
echo "FATAL: canonical VERSION not found in package()" >&2
exit 1
fi
_samios_ver_display="$(head -n1 "$_samios_ver_display_path")"
unset _samios_ver_display_path
# === Directory structure ===
install -d "${pkgdir}/usr/share/samios"
install -d "${pkgdir}/usr/share/samios/logos"
@@ -82,14 +147,18 @@ package() {
install -m644 "${srcdir}/samios-icon.svg" "${pkgdir}/usr/share/samios/icons/samios-icon.svg"
# === Branding metadata ===
# Display file uses the canonical VERSION verbatim (with hyphen if
# any). The Arch package version is in $pkgver (hyphen rewritten to
# underscore), and we want users reading /usr/share/samios/version
# to see the original string, not the package version.
cat > "${pkgdir}/usr/share/samios/version" <<EOF
SamiOS ${pkgver}
SamiOS ${_samios_ver_display}
EOF
cat > "${pkgdir}/usr/share/samios/branding.json" <<EOF
{
"name": "SamiOS",
"version": "${pkgver}",
"version": "${_samios_ver_display}",
"color_primary": "#1424CE",
"color_secondary": "#3B4ED8",
"publisher": "Sami Ahmed",
@@ -100,7 +169,7 @@ EOF
# === Default configuration ===
cat > "${pkgdir}/etc/samios/samios.conf" <<EOF
# SamiOS configuration
SAMIOS_VERSION=${pkgver}
SAMIOS_VERSION=${_samios_ver_display}
SAMIOS_COLOR_PRIMARY=#1424CE
SAMIOS_COLOR_SECONDARY=#3B4ED8
# Font policy: exclude fonts with 7777 in the name (personal branding)
+43 -3
View File
@@ -282,8 +282,28 @@ if [ -d /mnt/c/SamiOS/branding ]; then
cp /mnt/c/SamiOS/branding/* /usr/share/samios/ 2>/dev/null || true
fi
# Version file
echo "SamiOS 0.2.0 (WSL Desktop)" > /usr/share/samios/version
# Version file — generate the display string from the canonical VERSION.
# Resolution order: walk-up from script dir to ./VERSION, then
# /etc/samios-version (canonical mirror installed by the installer),
# then a documented placeholder. Same fallback chain as the samios
# CLI's _lookup_version so the two never disagree on what counts as
# "unresolved".
_SAMIOS_DESK_VERSION=""
_d="$(cd "$(dirname "$0")" && pwd)"
while [ "$_d" != "/" ]; do
if [ -f "$_d/VERSION" ]; then
_SAMIOS_DESK_VERSION="$(head -n1 "$_d/VERSION")"
break
fi
_d="$(dirname "$_d")"
done
if [ -z "$_SAMIOS_DESK_VERSION" ] && [ -r /etc/samios-version ]; then
_SAMIOS_DESK_VERSION="$(head -n1 /etc/samios-version)"
fi
[ -z "$_SAMIOS_DESK_VERSION" ] && _SAMIOS_DESK_VERSION="0.0.0-unknown"
unset _d
echo "SamiOS ${_SAMIOS_DESK_VERSION} (WSL Desktop)" > /usr/share/samios/version
unset _SAMIOS_DESK_VERSION
# SDDM theme override
mkdir -p /usr/share/sddm/themes/breeze
@@ -382,8 +402,28 @@ echo "--- Phase 9: SamiOS CLI ---"
cat > /usr/local/bin/samios << 'EOF'
#!/bin/bash
# SamiOS CLI (WSL desktop variant) — installed by samios-desktop-setup.sh
# No embedded VERSION literal: walks up to /etc/samios-version at runtime.
set -e
VERSION="0.2.0"
_lookup_version() {
local d
d="$(cd "$(dirname "$0")" && pwd)"
while [ "$d" != "/" ]; do
if [ -f "$d/VERSION" ]; then
head -n1 "$d/VERSION"
return 0
fi
d="$(dirname "$d")"
done
if [ -r /etc/samios-version ]; then
head -n1 /etc/samios-version
return 0
fi
echo "0.0.0-unknown"
}
VERSION="$(_lookup_version)"
unset -f _lookup_version
cmd_help() {
cat << HELPEOF
+65 -4
View File
@@ -4,7 +4,31 @@
# Usage: samios install [options]
set -e
VERSION="0.2.0"
# ── Canonical version source ────────────────────────────────────────────
# Walk up to find ./VERSION, fall back to /etc/samios-version, fall back
# to a placeholder. This guarantees the installer and the CLI it emits
# agree on the version string without any duplicate hardcoding.
_VERSION_FILE=""
_d="$(cd "$(dirname "$0")" && pwd)"
while [ "$_d" != "/" ]; do
if [ -f "$_d/VERSION" ]; then
_VERSION_FILE="$_d/VERSION"
break
fi
_d="$(dirname "$_d")"
done
if [ -z "$_VERSION_FILE" ] && [ -r /etc/samios-version ]; then
VERSION="$(head -n1 /etc/samios-version)"
elif [ -n "$_VERSION_FILE" ]; then
VERSION="$(head -n1 "$_VERSION_FILE")"
# Remember the file so install_samios_cli can mirror it onto the new
# system at /etc/samios-version.
_EMIT_VERSION_FILE="$_VERSION_FILE"
else
VERSION="0.0.0-unknown"
fi
unset _d _VERSION_FILE
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
@@ -258,15 +282,39 @@ create_user() {
# ============================================================
# SamiOS CLI tool
# ============================================================
# shellcheck disable=SC2120 # MOUNT_POINT arrives as a global from earlier steps
install_samios_cli() {
log "Installing SamiOS CLI tool..."
MOUNT_POINT="/mnt"
# Tolerate being called with no args; default to /mnt as every other
# step in the installer does. shellcheck SC2120 sees no args because
# MOUNT_POINT is a global from earlier steps.
local MOUNT_POINT="${1:-${MOUNT_POINT:-/mnt}}"
cat > "$MOUNT_POINT/usr/local/bin/samios" << 'SAMIOS_CLI'
#!/bin/bash
# SamiOS CLI - installed by samios-installer.sh
# No embedded VERSION literal: walks up to /etc/samios-version at runtime.
set -e
VERSION="0.2.0"
_lookup_version() {
local d
d="$(cd "$(dirname "$0")" && pwd)"
while [ "$d" != "/" ]; do
if [ -f "$d/VERSION" ]; then
head -n1 "$d/VERSION"
return 0
fi
d="$(dirname "$d")"
done
if [ -r /etc/samios-version ]; then
head -n1 /etc/samios-version
return 0
fi
echo "0.0.0-unknown"
}
VERSION="$(_lookup_version)"
unset -f _lookup_version
cmd_help() {
cat << EOF
@@ -340,6 +388,19 @@ esac
SAMIOS_CLI
chmod +x "$MOUNT_POINT/usr/local/bin/samios"
# Mirror the canonical VERSION file so the installed CLI's walk-up
# lookup resolves /etc/samios-version as a stable, non-relative anchor.
# The heredoc above writes a CLI with NO embedded VERSION literal —
# it walks up at runtime and reads from this file. This is the
# single-source-of-truth invariant: the literal exists ONLY in
# /etc/samios-version on disk, never inside any script.
if [ -n "$_EMIT_VERSION_FILE" ] && [ -f "$_EMIT_VERSION_FILE" ]; then
cp "$_EMIT_VERSION_FILE" "$MOUNT_POINT/etc/samios-version"
else
printf '%s\n' "$VERSION" > "$MOUNT_POINT/etc/samios-version"
fi
chmod 644 "$MOUNT_POINT/etc/samios-version"
# MOTD
cat > "$MOUNT_POINT/etc/motd" << 'MOTD'
+32 -1
View File
@@ -4,7 +4,7 @@ SamiOS Voice Command Bridge
Listens for voice commands from SamiType, routes them to Hermes or dictation mode.
Usage:
python3 voice-bridge.py
python3 voice-bridge.py [--version]
Requires:
- SamiType STT running in streaming mode (Unix socket or WebSocket)
@@ -21,6 +21,33 @@ import time
import threading
from enum import Enum
def _resolve_version() -> str:
"""Walk up from this script looking for ./VERSION (single source of
truth shared with the samios CLI, installer, and profiledef.sh).
Falls back to /etc/samios-version, then a placeholder."""
here = os.path.dirname(os.path.abspath(__file__))
d = here
while d != "/":
candidate = os.path.join(d, "VERSION")
if os.path.isfile(candidate):
try:
with open(candidate) as f:
return f.readline().strip()
except OSError:
pass
d = os.path.dirname(d)
try:
with open("/etc/samios-version") as f:
return f.readline().strip()
except OSError:
return "0.0.0-unknown"
VERSION = _resolve_version()
del _resolve_version
class VoiceMode(Enum):
IDLE = "idle"
COMMAND = "command"
@@ -59,6 +86,7 @@ class VoiceBridge:
def start(self):
"""Start the voice bridge"""
self.running = True
print(f"[VoiceBridge] SamiOS v{VERSION}")
print("[VoiceBridge] Ready. Say 'Computer' + command, or use hotkey.")
# Connect to SamiType STT
@@ -199,6 +227,9 @@ class VoiceBridge:
return None
def main():
if "--version" in sys.argv or "-V" in sys.argv:
print(f"SamiOS voice-bridge v{VERSION}")
sys.exit(0)
bridge = VoiceBridge()
try:
bridge.start()
+1
View File
@@ -24,6 +24,7 @@ TEST_FILES=(
"test_pacman_conf.sh"
"test_samios_cli.sh"
"test_font_policy.sh"
"test_version.sh"
)
TOTAL_RUN=0
+13 -1
View File
@@ -2,7 +2,13 @@
# Common test helpers for SamiOS test suite
# Sourced by individual test scripts
set -uo pipefail
# Note: deliberately no `set -u` here. Tests use variables in single-quoted
# bash -c subshells where shellcheck can see them but bash treats them as
# "set in a different scope". Adding -u causes spurious "unbound variable"
# errors when the subshell hasn't yet assigned a var the outer scope
# references (e.g. `assert "...$VAR..."`). `set -o pipefail` is enough to
# catch pipeline failures.
set -o pipefail
# ---------------------------------------------------------------------------
# Path setup — resolve repo root relative to this file
@@ -11,6 +17,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PROFILE_DIR="$REPO_ROOT/packaging/archiso"
SCRIPTS_DIR="$REPO_ROOT/packaging/scripts"
TESTS_DIR="$REPO_ROOT/tests"
# Export these so bash -c subshells (used by `assert ... bash -c '...'`)
# can reference them without re-derivation. test_version.sh relies on
# this for its git-clone mutation tests.
export REPO_ROOT PROFILE_DIR SCRIPTS_DIR TESTS_DIR
# ---------------------------------------------------------------------------
# Counters
+805
View File
@@ -0,0 +1,805 @@
#!/bin/bash
# Test: VERSION single-source-of-truth
# Locks the invariant: every version string in the repo must resolve to
# ./VERSION at runtime. No duplicate hardcoding allowed.
#
# Note: many assertions below reference variables (`cli_v`, `voice_v`,
# `mirror_v`, etc.) that are assigned inside single-quoted `bash -c`
# subshells. shellcheck cannot trace through the quoting layer, so it
# raises SC2154 warnings. These are intentional and the assertions DO
# verify the variables — see the test body.
# shellcheck disable=SC2154,SC2289,SC1011,SC1078,SC1083
source "$(dirname "$0")/test_helper.sh"
suite "VERSION single-source-of-truth tests"
VERSION_FILE="$REPO_ROOT/VERSION"
AIROOTFS_VERSION="$PROFILE_DIR/airootfs/etc/samios-version"
SAMIOS_CLI="$PROFILE_DIR/airootfs/usr/local/bin/samios"
INSTALLER="$SCRIPTS_DIR/samios-installer.sh"
PROFILEDEF="$PROFILE_DIR/profiledef.sh"
VOICE_BRIDGE="$SCRIPTS_DIR/voice-bridge.py"
PKGBUILD="$SCRIPTS_DIR/../packages/samios-branding/PKGBUILD"
DESKTOP_SETUP="$SCRIPTS_DIR/samios-desktop-setup.sh"
BASHRC="$PROFILE_DIR/airootfs/root/.bashrc"
ZSHRC="$PROFILE_DIR/airootfs/root/.zshrc"
# ── Hardcoded-release-version detector ────────────────────────────────────
# Scans a file for any X.Y.Z release-version literal, EXCLUDING:
# - lines starting with # (comments documenting SSOT design)
# - the documented fallback "0.0.0-unknown" string
# - test fixture strings like "9.9.9-test" and "0.2.0-rc1" which
# intentionally test version-set and prerelease flows
# The detector must catch ANY release-version literal, including:
# - version=1.2.3 (assignment)
# - "1.2.3" (string literal)
# - '1.2.3' (single-quoted string)
# - 1.2.3-foo (prerelease)
# - 1.2.3+bar (build metadata)
# - SamiOS 1.2.3 (display string)
# - VERSION=1.2.3 OR VERSION="1.2.3" (variable assignment)
detect_hardcoded_release() {
# detect_hardcoded_release <file>
# Exits 0 (clean) if no release-version literal is found; exits 1 if
# one is found.
#
# Production files: only the documented "0.0.0-unknown" fallback is
# allowed (matches the placeholder our resolvers print when the
# canonical VERSION cannot be located). Any other X.Y.Z literal
# (including prerelease, build-meta, v-prefixed, etc.) is a violation.
#
# Test files (under tests/): the same rule applies EXCEPT that two
# additional test fixtures are tolerated (0.2.0-rc1 in test 13's
# prerelease assertion, and 9.9.9-test in test 12's version-set
# assertion). Production components must NEVER contain these strings.
#
# Anchored allowed-token matching: each allowed token is matched as a
# complete regex match with non-alphanumeric boundaries on both sides.
# This prevents `127.0.0.10` from being eaten as `127.0.0.1` followed
# by `0` — `127.0.0.1` only matches at positions where the character
# AFTER it is non-alphanumeric, so `127.0.0.10` keeps its `0` intact
# and the version literal `127.0.0.10` is correctly flagged.
local f="$1"
local version_pattern='(^|[^a-zA-Z0-9])(v?[0-9]+[.][0-9]+[.][0-9]+([-+][a-zA-Z0-9.-]+)*)([^a-zA-Z0-9]|$)'
# Anchored allowed-token regex: same boundary rules as version_pattern.
# The middle group is the allowed token itself.
local allowed_pattern
if [[ "$f" == */tests/test_version.sh ]]; then
# Test fixture: tolerate the two prerelease/version-set fixtures
# AND the documented fallback + localhost IPs.
allowed_pattern='(^|[^a-zA-Z0-9])(0[.]0[.]0-unknown|9[.]9[.]9-test|0[.]2[.]0-rc1|127[.]0[.]0[.]1|127[.]0[.]1[.]1)([^a-zA-Z0-9]|$)'
else
# Production: only the documented 0.0.0-unknown fallback + localhost IPs
allowed_pattern='(^|[^a-zA-Z0-9])(0[.]0[.]0-unknown|127[.]0[.]0[.]1|127[.]0[.]1[.]1)([^a-zA-Z0-9]|$)'
fi
# Comment detection: a line is a comment if its first non-whitespace
# character is '#'. This handles shell, Python (after the leading
# whitespace), .desktop, and other line-oriented comment syntaxes.
local hits
# Strategy: find all version-pattern matches and allowed-pattern matches
# on each line. If a version-pattern match's character range does NOT
# fully overlap with an allowed-pattern match's range, it's a violation.
hits="$(grep -nE "$version_pattern" "$f" \
| grep -vE "^[^:]+:[ \\t]*#" \
| awk -F':' -v version_pat="$version_pattern" -v allowed_pat="$allowed_pattern" '
BEGIN { IGNORECASE = 0 }
{
content = substr($0, length($1) + 2)
# Build a "covered" map: for each allowed match, mark its
# character positions as covered. Then check each version
# match: if its range is NOT entirely covered, flag it.
delete covered
pos = 1
while (pos <= length(content) && match(substr(content, pos), allowed_pat)) {
s = pos + RSTART - 1
e = pos + RSTART + RLENGTH - 2
for (i = s; i <= e; i++) covered[i] = 1
# Advance past this match to find the next allowed match
pos = e + 1
}
# Now scan content for version matches and report any
# that are not fully covered.
vpos = 1
while (vpos <= length(content) && match(substr(content, vpos), version_pat)) {
vs = vpos + RSTART - 1
ve = vpos + RSTART + RLENGTH - 2
covered_full = 1
for (i = vs; i <= ve; i++) if (!covered[i]) { covered_full = 0; break }
if (!covered_full) { print $0; break }
vpos = ve + 1
}
}
')"
if [ -n "$hits" ]; then
echo "HARD-CODED RELEASE VERSIONS FOUND in $f:" >&2
echo "$hits" >&2
return 1
fi
return 0
}
# Export the function so it can be called from `bash -c '...'` subshells
export -f detect_hardcoded_release
# ── Repository-wide SSOT scan ────────────────────────────────────────────
# Every production file in the repo must pass detect_hardcoded_release.
# "Production" excludes tests/ (where we tolerate fixtures) and the
# canonical VERSION + airootfs mirror (which by definition contain
# the literal). If you add a new production file with a hardcoded
# version, it will appear here.
assert "every production file passes detect_hardcoded_release" \
bash -c '
violations=0
# Scan all production files. We exclude by EXACT canonical paths
# (not substring patterns), so a nested or unrelated file named
# VERSION cannot accidentally bypass SSOT enforcement.
while IFS= read -r -d "" f; do
case "$f" in
*/.git/*|*/tests/*) continue ;;
esac
# Compare against the exact canonical paths (resolved).
_resolved="$(cd "$(dirname "$f")" && pwd)/$(basename "$f")"
_root_version="$(cd "$REPO_ROOT" && pwd)/VERSION"
_root_mirror="$(cd "$REPO_ROOT" && pwd)/packaging/archiso/airootfs/etc/samios-version"
if [ "$_resolved" = "$_root_version" ] || [ "$_resolved" = "$_root_mirror" ]; then
continue
fi
if ! detect_hardcoded_release "$f"; then
violations=$((violations + 1))
fi
done < <(find "$REPO_ROOT" -type f -not -path "*/.git/*" -not -path "*/tests/*" -print0)
if [ "$violations" -gt 0 ]; then
echo "FAIL: $violations production files contain hardcoded version literals"
exit 1
fi
'
# ── Regression test: 0.2.0-rc1 is REJECTED in production components ──────
# The prerelease string "0.2.0-rc1" is allowed ONLY in test_version.sh as
# a test fixture. If it appears in any production component, the SSOT
# invariant is violated.
assert "regression: 0.2.0-rc1 is REJECTED in production components" \
bash -c '
# Find any production file containing 0.2.0-rc1 outside of test_version.sh
while IFS= read -r -d "" f; do
case "$f" in
*/.git/*|*/tests/*) continue ;;
esac
if grep -q "0\.2\.0-rc1" "$f" 2>/dev/null; then
echo "FAIL: $f contains 0.2.0-rc1 in production"
exit 1
fi
done < <(find "$REPO_ROOT" -type f -not -path "*/.git/*" -not -path "*/tests/*" -print0)
'
# ── Regression test: 9.9.9-test is REJECTED in production components ─────
# Same as above but for the version-set test fixture.
assert "regression: 9.9.9-test is REJECTED in production components" \
bash -c '
while IFS= read -r -d "" f; do
case "$f" in
*/.git/*|*/tests/*) continue ;;
esac
if grep -q "9\.9\.9-test" "$f" 2>/dev/null; then
echo "FAIL: $f contains 9.9.9-test in production"
exit 1
fi
done < <(find "$REPO_ROOT" -type f -not -path "*/.git/*" -not -path "*/tests/*" -print0)
'
# ── Detector self-tests ──────────────────────────────────────────────────
# The detector is a non-trivial regex; these tests verify it catches every
# form we claim and does not raise false positives. The tests construct
# synthetic files in $TMPDIR, run detect_hardcoded_release against them,
# and check the result. Each form is a separate assertion so a regression
# points at exactly which form broke.
assert "detector self-test: catches double-quoted X.Y.Z literal" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "echo \"1.2.3\"" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed \"1.2.3\"" >&2; exit 1
'
assert "detector self-test: catches single-quoted X.Y.Z literal" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "echo '"'"'1.2.3'"'"'" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed '"'"'1.2.3'"'"'" >&2; exit 1
'
assert "detector self-test: catches bare X.Y.Z literal" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "echo 1.2.3" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed bare 1.2.3" >&2; exit 1
'
assert "detector self-test: catches prerelease X.Y.Z-foo literal" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "echo 1.2.3-rc1" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed 1.2.3-rc1" >&2; exit 1
'
assert "detector self-test: catches build-metadata X.Y.Z+bar literal" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "echo 1.2.3+build.5" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed 1.2.3+build.5" >&2; exit 1
'
assert "detector self-test: catches SamiOS X.Y.Z display string" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "echo \"SamiOS 1.2.3 (WSL Desktop)\"" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed SamiOS 1.2.3" >&2; exit 1
'
assert "detector self-test: catches VERSION=1.2.3 assignment" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "VERSION=\"1.2.3\"" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed VERSION=1.2.3" >&2; exit 1
'
assert "detector self-test: catches v-prefixed v1.2.3" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "echo v1.2.3" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed v1.2.3" >&2; exit 1
'
assert "detector self-test: catches X.Y.Z in path (/opt/1.2.3/bin)" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "PATH=/opt/1.2.3/bin" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed path-embedded 1.2.3" >&2; exit 1
'
assert "detector self-test: catches X.Y.Z in colon-delimited value" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "key=val:1.2.3:rest" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed colon-delimited 1.2.3" >&2; exit 1
'
assert "detector self-test: catches X.Y.Z in brackets [1.2.3]" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "x=[1.2.3]" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed bracketed 1.2.3" >&2; exit 1
'
assert "detector self-test: ALLOWS 0.0.0-unknown fallback" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "VERSION=0.0.0-unknown" > "$tmpf"
detect_hardcoded_release "$tmpf" && exit 0
echo "FAILED: detector flagged documented fallback 0.0.0-unknown" >&2; exit 1
'
assert "detector self-test: ALLOWS comments (lines starting with #)" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "# This is a comment with 1.2.3 in it" > "$tmpf"
detect_hardcoded_release "$tmpf" && exit 0
echo "FAILED: detector flagged commented-out version" >&2; exit 1
'
assert "detector self-test: ALLOWS localhost IP (127.0.0.1)" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "127.0.0.1 localhost" > "$tmpf"
detect_hardcoded_release "$tmpf" && exit 0
echo "FAILED: detector flagged localhost IP" >&2; exit 1
'
assert "detector self-test: catches MIXED allowed+forbidden on same line" \
bash -c '
# A line that contains BOTH the allowed fallback and a forbidden
# release-version literal must be flagged. This catches the bug
# where `grep -v '\''0.0.0-unknown'\''` would suppress the entire line
# and miss the forbidden literal.
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "echo 0.0.0-unknown 1.2.3" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed mixed allowed/forbidden line" >&2; exit 1
'
assert "detector self-test: ALLOWS indented # comments with version literal" \
bash -c '
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
cat > "$tmpf" << '"'"'EOF'"'"'
# Top-level comment with 1.2.3
# Indented comment with 1.2.4
echo real_code
EOF
detect_hardcoded_release "$tmpf" && exit 0
echo "FAILED: detector flagged commented version literals" >&2; exit 1
'
assert "detector self-test: catches 127.0.0.10 (substring of allowed IP)" \
bash -c '
# 127.0.0.10 contains 127.0.0.1 as a substring. A naive substring
# filter would erase `127.0.0.1` and leave `0` (which does NOT
# match X.Y.Z), letting the real release-version literal
# `127.0.0.10` slip through. Our anchored detector must catch it.
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "echo 127.0.0.10" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed 127.0.0.10" >&2; exit 1
'
assert "detector self-test: catches 0.0.0-unknown+build (suffix of fallback)" \
bash -c '
# 0.0.0-unknown+build contains 0.0.0-unknown as a substring. A naive
# filter would erase `0.0.0-unknown` and leave `+build` (which
# does NOT match X.Y.Z), letting the build-metadata version literal
# `0.0.0-unknown+build` slip through.
tmpf="$(mktemp)"
trap "rm -f '\''$tmpf'\''" EXIT
echo "echo 0.0.0-unknown+build" > "$tmpf"
detect_hardcoded_release "$tmpf" || exit 0
echo "FAILED: detector missed 0.0.0-unknown+build" >&2; exit 1
'
# ── 1. Canonical file exists and is a valid semver-ish string ──────────────
assert_file_exists "VERSION file exists at repo root" "$VERSION_FILE"
assert "VERSION is non-empty single-line" \
bash -c '
[ "$(wc -l < "'"$VERSION_FILE"'")" -eq 1 ] && \
[ -n "$(cat "'"$VERSION_FILE"'")" ]
'
assert "VERSION matches semver-ish pattern (X.Y.Z optional suffix)" \
bash -c '
grep -qE "^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$" "'"$VERSION_FILE"'"
'
# ── 2. airootfs mirror is byte-identical to root VERSION ───────────────────
assert_file_exists "airootfs mirror /etc/samios-version exists" "$AIROOTFS_VERSION"
assert "airootfs VERSION mirror matches repo root VERSION" \
bash -c '
diff -q "'"$VERSION_FILE"'" "'"$AIROOTFS_VERSION"'" >/dev/null
'
# ── 3. samios CLI must NOT hardcode any release-version literal ──────────
assert_file_exists "samios CLI exists" "$SAMIOS_CLI"
assert "samios CLI has no hardcoded release-version literal" \
bash -c 'detect_hardcoded_release "'"$SAMIOS_CLI"'"'
assert "samios CLI references ./VERSION walk-up lookup" \
bash -c '
grep -q "/VERSION" "'"$SAMIOS_CLI"'" && \
grep -q "_lookup_version" "'"$SAMIOS_CLI"'"
'
assert "samios CLI prints repo VERSION when run from repo root" \
bash -c '
cd "'"$REPO_ROOT"'" && \
expected="$(cat VERSION)" && \
actual="$(bash "'"$SAMIOS_CLI"'" version | sed -E "s/^SamiOS v//" | head -n1)" && \
[ "$actual" = "$expected" ]
'
# ── 4. Installer must NOT hardcode any release-version literal ───────────
assert_file_exists "samios-installer.sh exists" "$INSTALLER"
assert "installer has no hardcoded release-version literal" \
bash -c 'detect_hardcoded_release "'"$INSTALLER"'"'
assert "installer references ./VERSION walk-up lookup" \
bash -c '
grep -q "_VERSION_FILE\|/VERSION" "'"$INSTALLER"'"
'
# Verify the installer's actual heredoc-emitted CLI has the _lookup_version
# function — not a substituted $VERSION literal.
assert "installer heredoc emits CLI with _lookup_version (no literal)" \
bash -c '
# The installer MUST NOT substitute $VERSION into a literal at
# the top of the emitted CLI. We extract the heredoc body (between
# the << SAMIOS_CLI marker line and the closing SAMIOS_CLI) and
# check for the forbidden pattern.
heredoc_open="$(grep -n "<< .*SAMIOS_CLI" "'"$INSTALLER"'" | head -1 | cut -d: -f1)"
heredoc_close="$(awk -v open_line="$heredoc_open" "NR>open_line && /^SAMIOS_CLI\$/{print NR; exit}" "'"$INSTALLER"'")"
if [ -z "$heredoc_open" ] || [ -z "$heredoc_close" ]; then
echo "could not locate SAMIOS_CLI heredoc in installer (open=$heredoc_open close=$heredoc_close)"
exit 1
fi
heredoc_body="$(sed -n "${heredoc_open},${heredoc_close}p" "'"$INSTALLER"'")"
# Forbidden: a literal VERSION="${VERSION}" (substituted from outer scope)
if echo "$heredoc_body" | grep -qE "^VERSION=\"\\\$\{VERSION\}\""; then
echo "installer emits VERSION=\"\${VERSION}\" literal at top of heredoc"
exit 1
fi
# Required: the heredoc body must reference _lookup_version so
# the installed CLI uses the walk-up pattern.
if ! echo "$heredoc_body" | grep -q "_lookup_version"; then
echo "installer heredoc does not reference _lookup_version"
exit 1
fi
'
# ── 5. profiledef.sh must NOT hardcode any release-version literal ────────
assert_file_exists "profiledef.sh exists" "$PROFILEDEF"
assert "profiledef.sh has no hardcoded release-version literal" \
bash -c 'detect_hardcoded_release "'"$PROFILEDEF"'"'
assert "profiledef.sh references ./VERSION walk-up lookup" \
bash -c '
grep -q "_iso_version\|/VERSION" "'"$PROFILEDEF"'"
'
# ── 6. voice-bridge.py must read from ./VERSION ────────────────────────────
assert_file_exists "voice-bridge.py exists" "$VOICE_BRIDGE"
assert "voice-bridge.py parses as valid Python" \
bash -c '
python3 -c "import ast; ast.parse(open(\"'"$VOICE_BRIDGE"'\").read())"
'
assert "voice-bridge.py has no hardcoded release-version literal" \
bash -c 'detect_hardcoded_release "'"$VOICE_BRIDGE"'"'
assert "voice-bridge.py resolves VERSION from repo root" \
bash -c '
cd "'"$REPO_ROOT"'" && \
expected="$(cat VERSION)" && \
actual="$(python3 "'"$VOICE_BRIDGE"'" --version | sed -E "s/^SamiOS voice-bridge v//")" && \
[ "$actual" = "$expected" ]
'
# ── 7. PKGBUILD must NOT hardcode any release-version literal ────────────
assert_file_exists "PKGBUILD exists" "$PKGBUILD"
assert "PKGBUILD has no hardcoded release-version literal" \
bash -c 'detect_hardcoded_release "'"$PKGBUILD"'"'
assert "PKGBUILD references ../../../VERSION" \
bash -c '
grep -q "../../../VERSION" "'"$PKGBUILD"'"
'
# ── 8. desktop-setup.sh must NOT hardcode any release-version literal ──────
assert_file_exists "samios-desktop-setup.sh exists" "$DESKTOP_SETUP"
assert "desktop-setup.sh has no hardcoded release-version literal" \
bash -c 'detect_hardcoded_release "'"$DESKTOP_SETUP"'"'
# ── 9. .bashrc / .zshrc must NOT hardcode any release-version literal ────
assert ".bashrc has no hardcoded release-version literal" \
bash -c 'detect_hardcoded_release "'"$BASHRC"'"'
assert ".zshrc has no hardcoded release-version literal" \
bash -c 'detect_hardcoded_release "'"$ZSHRC"'"'
# ── 10. Cross-system invariant: CLI, installer, profiledef, voice-bridge ──
# Test the actual code paths in each file. For the installer and profiledef,
# we extract their version-resolution blocks from the real files (not
# reimplementations) into temp helpers, then rewrite the $0-relative path
# to point at the actual file's directory so the walk-up starts in the
# right place.
PROFILEDEF_RESOLVER="$(mktemp)"
PROFILEDEF_DIR="$(dirname "$PROFILEDEF")"
{
sed -n '/^# ── Canonical version source ───/,/^unset _d$/p' \
"$PROFILEDEF"
echo 'echo "$_iso_version"'
} > "$PROFILEDEF_RESOLVER"
sed -i "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$PROFILEDEF_DIR\"|g" "$PROFILEDEF_RESOLVER"
chmod +x "$PROFILEDEF_RESOLVER"
# Extract the installer's version-resolution block (top of file) into a
# real shell script we can run. The block uses $0 to find its own
# directory; when extracted standalone, $0 becomes the helper's path
# (under /tmp), which isn't where VERSION lives. We rewrite the $0
# lookup to start from the installer's actual directory.
INSTALLER_DIR="$(dirname "$INSTALLER")"
INSTALLER_RESOLVER="$(mktemp)"
{
sed -n '/^# ── Canonical version source ───/,/^unset _d _VERSION_FILE/p' \
"$INSTALLER"
echo 'echo "$VERSION"'
} > "$INSTALLER_RESOLVER"
# Rewrite $0 references inside the extracted block so they resolve from
# the installer's actual directory, not from /tmp/codex-judge-XXXXXX.
sed -i "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$INSTALLER_DIR\"|g" "$INSTALLER_RESOLVER"
chmod +x "$INSTALLER_RESOLVER"
assert "all four components agree on VERSION (tested from real files)" \
bash -c '
cd "'"$REPO_ROOT"'" || exit 1
expected="$(cat VERSION)"
cli_v="$(bash "'"$SAMIOS_CLI"'" version | head -n1 | sed -E "s/^SamiOS v//")"
voice_v="$(python3 "'"$VOICE_BRIDGE"'" --version | sed -E "s/^SamiOS voice-bridge v//")"
inst_v="$("'"$INSTALLER_RESOLVER"'" | tail -n1)"
prof_v="$("'"$PROFILEDEF_RESOLVER"'")"
if [ "$cli_v" != "$expected" ]; then
echo "CLI=$cli_v expected=$expected"; exit 1
fi
if [ "$voice_v" != "$expected" ]; then
echo "voice=$voice_v expected=$expected"; exit 1
fi
if [ "$inst_v" != "$expected" ]; then
echo "installer=$inst_v expected=$expected"; exit 1
fi
if [ "$prof_v" != "$expected" ]; then
echo "profiledef=$prof_v expected=$expected"; exit 1
fi
'
rm -f "$PROFILEDEF_RESOLVER" "$INSTALLER_RESOLVER"
# ── 11. PKGBUILD resolves pkgver to the canonical VERSION ──────────────────
# Source the PKGBUILD in a subshell (PKGBUILD is bash-runnable) and capture
# the resulting pkgver. This proves the conditional `pkgver=` block in the
# PKGBUILD evaluates to the canonical VERSION.
assert "PKGBUILD pkgver resolves to VERSION (sourced)" \
bash -c '
cd "'"$REPO_ROOT"'"/packaging/packages/samios-branding && \
expected="$(head -n1 ../../../VERSION)" && \
actual="$(set -e; source PKGBUILD >/dev/null 2>&1 && echo "$pkgver")" && \
[ "$actual" = "$expected" ] || { echo "actual=$actual expected=$expected"; exit 1; }
'
# ── 11b. PKGBUILD SSOT is absolute (no SAMIOS_PKGVER_OVERRIDE escape) ─────
# The previous PKGBUILD had a SAMIOS_PKGVER_OVERRIDE env var that allowed
# pkgver to differ from VERSION. That violated the SSOT invariant. Verify
# the override is removed so release builds cannot diverge.
assert "PKGBUILD has no SAMIOS_PKGVER_OVERRIDE escape hatch (SSOT is absolute)" \
bash -c '
# Look for SAMIOS_PKGVER_OVERRIDE outside of comments (lines
# starting with #). Comments can mention the variable for
# documentation purposes; only executable code that references
# it would violate SSOT.
if grep -n "SAMIOS_PKGVER_OVERRIDE" "'"$PKGBUILD"'" | grep -vE "^[0-9]+:#"; then
echo "PKGBUILD still references SAMIOS_PKGVER_OVERRIDE outside a comment — SSOT invariant violated"
grep -n "SAMIOS_PKGVER_OVERRIDE" "'"$PKGBUILD"'" | grep -vE "^[0-9]+:#"
exit 1
fi
# Also verify the override does not actually bypass VERSION, in
# case some future shell magic reintroduces the override.
cd "'"$REPO_ROOT"'"/packaging/packages/samios-branding && \
expected="$(head -n1 ../../../VERSION)" && \
actual="$(SAMIOS_PKGVER_OVERRIDE=99.99.99-bogus bash -c "set -e; source PKGBUILD >/dev/null 2>&1; echo \$pkgver")" && \
[ "$actual" = "$expected" ] || { echo "override leaked: actual=$actual expected=$expected"; exit 1; }
'
assert "regression: hash-based diff catches deletions (not just additions)" \
bash -c '
# The version-set test (assertion above) counts only the right
# side of the diff. A pure deletion produces only a left-side
# line. Verify the diff-based detection we use catches BOTH
# sides: synthesize a fake before/after where a file is deleted
# and a different file is added, then count unique filenames.
diff_file="$(mktemp)"
trap "rm -f '\''$diff_file'\''" EXIT
cat > "$diff_file" << '"'"'DIFF_EOF'"'"'
< d84523f8671da4c8b99f543127715bb8c562fafe8675016112c6d260d14e761b ./some-deleted-file.txt
> 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef ./some-new-file.txt
DIFF_EOF
count="$(awk '"'"'/^[<>] / {print $NF}'"'"' "$diff_file" | sort -u | wc -l)"
if [ "$count" -ne 2 ]; then
echo "FAIL: expected 2 unique filenames (one deleted + one added), got $count"
exit 1
fi
'
# ── 12. End-to-end: version-set mutates only VERSION + mirror ─────────────
assert "version-set mutates only VERSION + airootfs mirror (no other files)" \
bash -c '
clone="$(mktemp -d)"
diff_file="$(mktemp)"
# Cleanup trap uses $clone and $diff_file from the surrounding scope
trap "rm -rf ${clone:?} ${diff_file:?}" EXIT
# Copy with .git so any pre-existing modifications are visible as
# baseline (so we can detect whether version-set changed anything
# by content, not by index state).
cp -a "$REPO_ROOT" "$clone/repo"
cd "$clone/repo" || exit 1
orig_version="$(cat VERSION)"
orig_mirror="$(head -n1 packaging/archiso/airootfs/etc/samios-version)"
# Snapshot the content hash of every file in the repo (excluding
# .git) BEFORE the mutation. We use this to detect any file change,
# not git status which can be misleading for already-modified files.
before_hashes="$(find . -path ./.git -prune -o -type f -print0 2>/dev/null \
| xargs -0 sha256sum 2>/dev/null | sort)"
# `make version-set` must SUCCEED. If it fails (e.g. bad NEW value,
# missing make), the test fails — not silently passes.
if ! make version-set NEW=9.9.9-test >/dev/null 2>&1; then
echo "make version-set NEW=9.9.9-test failed unexpectedly"
exit 1
fi
# VERSION must actually have changed.
if [ "$(cat VERSION)" != "9.9.9-test" ]; then
echo "VERSION did not change to 9.9.9-test"
exit 1
fi
# Mirror must actually have changed.
if [ "$(head -n1 packaging/archiso/airootfs/etc/samios-version)" != "9.9.9-test" ]; then
echo "airootfs mirror did not sync to 9.9.9-test"
exit 1
fi
# Snapshot hashes AFTER the mutation.
after_hashes="$(find . -path ./.git -prune -o -type f -print0 2>/dev/null \
| xargs -0 sha256sum 2>/dev/null | sort)"
# Diff before vs after. Count BOTH sides of the diff (a deletion
# produces only a left-side line; an addition produces only a
# right-side line; a modification produces one of each). Use the
# parsed filename-to-hash maps to count unique files that
# changed in either direction, not just added/right-side lines.
diff <(echo "$before_hashes") <(echo "$after_hashes") > "$diff_file"
# Extract the set of filenames that appear on either side of the
# diff. `awk` gets us the distinct filenames regardless of side.
changed_count="$(awk "/^[<>] / {print \$NF}" "$diff_file" | sort -u | wc -l)"
if [ "$changed_count" -ne 2 ]; then
echo "expected exactly 2 files to change (VERSION + mirror), got $changed_count:"
awk "/^[<>] / {print \$0}" "$diff_file"
exit 1
fi
# Verify the two changed files are VERSION and the mirror (not
# some other files). The diff format is "<sha256> <filename>";
# match on the filename suffix.
unexpected="$(awk "/^[<>] / {print \$NF}" "$diff_file" | sort -u)"
unexpected="$(echo "$unexpected" | grep -vE "VERSION$|packaging/archiso/airootfs/etc/samios-version$")"
if [ -n "$unexpected" ]; then
echo "version-set changed unexpected files:"
echo "$unexpected"
exit 1
fi
# Restore clone state — the developers checkout was never touched.
# We restore to the original VERSION we captured, not to a
# hardcoded value, so this test is safe to run at any version.
echo "$orig_version" > VERSION
if ! make version-sync >/dev/null 2>&1; then
echo "make version-sync failed during restore"
exit 1
fi
# Verify restoration succeeded by snapshotting again and comparing
# to the original before-snapshot. The post-restore state must
# match the original content hash (no stray modifications).
restore_hashes="$(find . -path ./.git -prune -o -type f -print0 2>/dev/null \
| xargs -0 sha256sum 2>/dev/null | sort)"
if [ "$before_hashes" != "$restore_hashes" ]; then
echo "restore failed — clone does not match original"
diff <(echo "$before_hashes") <(echo "$restore_hashes") | head -10
exit 1
fi
# Sanity: the values we saved at the top must be the canonical
# values, not arbitrary ones. (We already know VERSION=0.1.0 from
# earlier tests, but this guards against the test running against
# an unusual repo state.)
if [ -z "$orig_version" ] || [ -z "$orig_mirror" ]; then
echo "could not read original VERSION or mirror; bail"
exit 1
fi
'
# ── 13. Prerelease VERSION works through the whole stack ─────────────────
# Mutate VERSION to a prerelease and verify ALL resolvers see it correctly:
# CLI, voice-bridge, airootfs mirror, installer (resolves through its own
# walk-up), profiledef (resolves through its own walk-up), desktop-setup
# (resolves through its own walk-up), and PKGBUILD (transforms to underscore).
#
# Use a cleanup trap and preserve the original VERSION rather than
# hardcoding 0.1.0, so this test is safe to run at any version.
assert "prerelease VERSION (e.g. 0.2.0-rc1) flows through all 7 components" \
bash -c '
clone="$(mktemp -d)"
trap "rm -rf '\''$clone'\''" EXIT
cp -a "$REPO_ROOT" "$clone/repo"
cd "$clone/repo" || exit 1
orig_version="$(cat VERSION)"
echo "0.2.0-rc1" > VERSION
if ! make version-sync >/dev/null 2>&1; then
echo "make version-sync failed"; exit 1
fi
cli_v="$(bash packaging/archiso/airootfs/usr/local/bin/samios version | head -n1 | sed -E "s/^SamiOS v//")"
voice_v="$(python3 packaging/scripts/voice-bridge.py --version | sed -E "s/^SamiOS voice-bridge v//")"
mirror_v="$(head -n1 packaging/archiso/airootfs/etc/samios-version)"
# Installer resolves through its own walk-up block at the top of the file
installer_resolver="$(mktemp)"
sed -n "/^# ── Canonical version source ───/,/^unset _d _VERSION_FILE/p" \
packaging/scripts/samios-installer.sh | sed "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$(pwd)/packaging/scripts\"|g" \
> "$installer_resolver"
printf "echo \"\$VERSION\"\n" >> "$installer_resolver"
chmod +x "$installer_resolver"
installer_v="$("$installer_resolver" | tail -n1)"
rm -f "$installer_resolver"
# Profiledef resolves through its own walk-up block
profiledef_resolver="$(mktemp)"
sed -n "/^# ── Canonical version source ───/,/^unset _d$/p" \
packaging/archiso/profiledef.sh | sed "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$(pwd)/packaging/archiso\"|g" \
> "$profiledef_resolver"
printf "echo \"\$_iso_version\"\n" >> "$profiledef_resolver"
chmod +x "$profiledef_resolver"
profiledef_v="$("$profiledef_resolver" | tail -n1)"
rm -f "$profiledef_resolver"
# Desktop-setup resolves through its own walk-up block. Extract
# the resolution block up to (but not including) the
# echo-write line — that line writes to a system path we cant
# touch in a test.
desktop_start="$(grep -n "# Version file — generate" packaging/scripts/samios-desktop-setup.sh | head -1 | cut -d: -f1)"
desktop_end_pre="$(grep -n "echo \"SamiOS.*> /usr/share/samios/version" packaging/scripts/samios-desktop-setup.sh | head -1 | cut -d: -f1)"
desktop_end=$((desktop_end_pre - 1))
desktop_resolver="$(mktemp)"
if [ -z "$desktop_start" ] || [ -z "$desktop_end_pre" ]; then
echo "could not locate desktop-setup version block markers"
exit 1
fi
sed -n "${desktop_start},${desktop_end}p" packaging/scripts/samios-desktop-setup.sh | \
sed "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$(pwd)/packaging/scripts\"|g" \
> "$desktop_resolver"
printf "echo \"\$_SAMIOS_DESK_VERSION\"\n" >> "$desktop_resolver"
chmod +x "$desktop_resolver"
desktop_v="$("$desktop_resolver" | tail -n1)"
rm -f "$desktop_resolver"
# PKGBUILD transforms hyphen to underscore for Arch pkgver
pkgver_v="$(cd packaging/packages/samios-branding && set -e && source PKGBUILD >/dev/null 2>&1 && echo "${pkgver:-}")"
# Restore clone to its original VERSION (captured above) rather
# than hardcoding a value — safe at any current version.
echo "$orig_version" > VERSION
make version-sync >/dev/null 2>&1
# Verify all seven agree (or transform as expected)
[ "$cli_v" = "0.2.0-rc1" ] || { echo "CLI=$cli_v"; exit 1; }
[ "$voice_v" = "0.2.0-rc1" ] || { echo "voice=$voice_v"; exit 1; }
[ "$mirror_v" = "0.2.0-rc1" ] || { echo "mirror=$mirror_v"; exit 1; }
[ "$installer_v" = "0.2.0-rc1" ] || { echo "installer=$installer_v"; exit 1; }
[ "$profiledef_v" = "0.2.0-rc1" ] || { echo "profiledef=$profiledef_v"; exit 1; }
[ "$desktop_v" = "0.2.0-rc1" ] || { echo "desktop=$desktop_v"; exit 1; }
[ "$pkgver_v" = "0.2.0_rc1" ] || { echo "pkgver=$pkgver_v expected=0.2.0_rc1"; exit 1; }
'
# ── 14. PKGBUILD transforms "0.X.Y-suffix" to "0.X.Y_suffix" for Arch ───
# This duplicates test 13's PKGBUILD check but in isolation, so a failure
# here points directly at PKGBUILD (not at the broader end-to-end test).
assert "PKGBUILD transforms prerelease hyphen to underscore for Arch pkgver" \
bash -c '
clone="$(mktemp -d)"
trap "rm -rf ${clone:?}" EXIT
cp -a "$REPO_ROOT" "$clone/repo"
cd "$clone/repo/packaging/packages/samios-branding" || exit 1
echo "0.2.0-rc1" > ../../../VERSION
# Source PKGBUILD and echo pkgver — no nested bash -c quoting needed.
pkgver_actual="$(set -e; source PKGBUILD >/dev/null 2>&1 && echo "$pkgver")"
cd /
rm -rf "$clone"
[ "$pkgver_actual" = "0.2.0_rc1" ] || { echo "pkgver=$pkgver_actual expected=0.2.0_rc1"; exit 1; }
'
print_summary