[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 ────────────────────────────────────────────────────────────── # ── Paths ──────────────────────────────────────────────────────────────
REPO_ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) REPO_ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
PROFILE_DIR := $(REPO_ROOT)/packaging/archiso PROFILE_DIR := $(REPO_ROOT)/packaging/archiso
SCRIPTS_DIR := $(REPO_ROOT)/packaging/scripts SCRIPTS_DIR := $(REPO_ROOT)/packaging/scripts
TESTS_DIR := $(REPO_ROOT)/tests TESTS_DIR := $(REPO_ROOT)/tests
VERSION_FILE := $(REPO_ROOT)/VERSION
# Shell scripts to lint with shellcheck # Shell scripts to lint with shellcheck
SHELL_SCRIPTS := \ SHELL_SCRIPTS := \
@@ -14,12 +15,14 @@ SHELL_SCRIPTS := \
$(PROFILE_DIR)/airootfs/usr/local/bin/choose-mirror \ $(PROFILE_DIR)/airootfs/usr/local/bin/choose-mirror \
$(PROFILE_DIR)/airootfs/root/.automated_script.sh \ $(PROFILE_DIR)/airootfs/root/.automated_script.sh \
$(SCRIPTS_DIR)/samios-desktop-setup.sh \ $(SCRIPTS_DIR)/samios-desktop-setup.sh \
$(SCRIPTS_DIR)/samios-installer.sh \
$(TESTS_DIR)/test_helper.sh \ $(TESTS_DIR)/test_helper.sh \
$(TESTS_DIR)/test_profiledef.sh \ $(TESTS_DIR)/test_profiledef.sh \
$(TESTS_DIR)/test_packages.sh \ $(TESTS_DIR)/test_packages.sh \
$(TESTS_DIR)/test_pacman_conf.sh \ $(TESTS_DIR)/test_pacman_conf.sh \
$(TESTS_DIR)/test_samios_cli.sh \ $(TESTS_DIR)/test_samios_cli.sh \
$(TESTS_DIR)/test_font_policy.sh \ $(TESTS_DIR)/test_font_policy.sh \
$(TESTS_DIR)/test_version.sh \
$(TESTS_DIR)/run_tests.sh $(TESTS_DIR)/run_tests.sh
SHELLCHECK ?= shellcheck SHELLCHECK ?= shellcheck
@@ -58,3 +61,31 @@ clean: ## Remove build artifacts
@rm -rf /tmp/samios-build @rm -rf /tmp/samios-build
@rm -rf $(PROFILE_DIR)/out @rm -rf $(PROFILE_DIR)/out
@echo "Clean complete" @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 Copyright=(c) 2026 Sami Ahmed
License=GPL-3.0 License=GPL-3.0
Type=sddm-theme Type=sddm-theme
Version=0.2.0
Website=https://sami-ahmed.net Website=https://sami-ahmed.net
Screenshot=background.png 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 # Welcome message — printed once per interactive login shell
# ============================================================ # ============================================================
if shopt -q login_shell; then 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 ""
echo -e " \033[1;34m ▲\033[0m \033[1;34mSamiOS\033[0m \033[0;37m${SAMIOS_VER}\033[0m" 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" 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 if [[ -o login ]]; then
echo "" 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 |\\\ 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 ""
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 " \033[0;37mCommands:\033[0m \033[1;msamios help\033[0m · \033[1;msamios status\033[0m · \033[1;msamios fonts\033[0m"
echo "" echo ""
@@ -3,7 +3,31 @@
set -e 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" CONFIG_DIR="/etc/samios"
LOG_FILE="/var/log/samios.log" LOG_FILE="/var/log/samios.log"
+19 -1
View File
@@ -4,11 +4,28 @@
# SamiOS archiso profile definition # SamiOS archiso profile definition
# Minimal CLI-based Arch Linux with SamiOS branding # 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_name="samios"
iso_label="SAMIOS_$(date +%Y%m)" iso_label="SAMIOS_$(date +%Y%m)"
iso_publisher="Sami Ahmed <sami@sami-ahmed.net>" iso_publisher="Sami Ahmed <sami@sami-ahmed.net>"
iso_application="SamiOS Live/Rescue CD" iso_application="SamiOS Live/Rescue CD"
iso_version="0.1.0" iso_version="${_iso_version}"
unset _iso_version
install_dir="arch" install_dir="arch"
buildmodes=('iso') buildmodes=('iso')
bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito' 'uefi-x64.grub.esp' 'uefi-x64.grub.eltorito') 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" ["/root/.gnupg"]="0:0:700"
["/usr/local/bin/choose-mirror"]="0:0:755" ["/usr/local/bin/choose-mirror"]="0:0:755"
["/usr/local/bin/samios"]="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 # then run `makepkg -f` from within it. Alternatively run the build helper
# packaging/packages/samios-branding/fetch-assets.sh to pull them in from # packaging/packages/samios-branding/fetch-assets.sh to pull them in from
# the repo branding/ tree. # 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 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 pkgrel=1
pkgdesc="SamiOS branding assets — logos, icons, GRUB theme, and default configuration" pkgdesc="SamiOS branding assets — logos, icons, GRUB theme, and default configuration"
arch=('any') arch=('any')
@@ -43,6 +84,30 @@ sha256sums=(
) )
package() { 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 === # === Directory structure ===
install -d "${pkgdir}/usr/share/samios" install -d "${pkgdir}/usr/share/samios"
install -d "${pkgdir}/usr/share/samios/logos" 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" install -m644 "${srcdir}/samios-icon.svg" "${pkgdir}/usr/share/samios/icons/samios-icon.svg"
# === Branding metadata === # === 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 cat > "${pkgdir}/usr/share/samios/version" <<EOF
SamiOS ${pkgver} SamiOS ${_samios_ver_display}
EOF EOF
cat > "${pkgdir}/usr/share/samios/branding.json" <<EOF cat > "${pkgdir}/usr/share/samios/branding.json" <<EOF
{ {
"name": "SamiOS", "name": "SamiOS",
"version": "${pkgver}", "version": "${_samios_ver_display}",
"color_primary": "#1424CE", "color_primary": "#1424CE",
"color_secondary": "#3B4ED8", "color_secondary": "#3B4ED8",
"publisher": "Sami Ahmed", "publisher": "Sami Ahmed",
@@ -100,7 +169,7 @@ EOF
# === Default configuration === # === Default configuration ===
cat > "${pkgdir}/etc/samios/samios.conf" <<EOF cat > "${pkgdir}/etc/samios/samios.conf" <<EOF
# SamiOS configuration # SamiOS configuration
SAMIOS_VERSION=${pkgver} SAMIOS_VERSION=${_samios_ver_display}
SAMIOS_COLOR_PRIMARY=#1424CE SAMIOS_COLOR_PRIMARY=#1424CE
SAMIOS_COLOR_SECONDARY=#3B4ED8 SAMIOS_COLOR_SECONDARY=#3B4ED8
# Font policy: exclude fonts with 7777 in the name (personal branding) # 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 cp /mnt/c/SamiOS/branding/* /usr/share/samios/ 2>/dev/null || true
fi fi
# Version file # Version file — generate the display string from the canonical VERSION.
echo "SamiOS 0.2.0 (WSL Desktop)" > /usr/share/samios/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 # SDDM theme override
mkdir -p /usr/share/sddm/themes/breeze mkdir -p /usr/share/sddm/themes/breeze
@@ -382,8 +402,28 @@ echo "--- Phase 9: SamiOS CLI ---"
cat > /usr/local/bin/samios << 'EOF' cat > /usr/local/bin/samios << 'EOF'
#!/bin/bash #!/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 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() { cmd_help() {
cat << HELPEOF cat << HELPEOF
+65 -4
View File
@@ -4,7 +4,31 @@
# Usage: samios install [options] # Usage: samios install [options]
set -e 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' RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
@@ -258,15 +282,39 @@ create_user() {
# ============================================================ # ============================================================
# SamiOS CLI tool # SamiOS CLI tool
# ============================================================ # ============================================================
# shellcheck disable=SC2120 # MOUNT_POINT arrives as a global from earlier steps
install_samios_cli() { install_samios_cli() {
log "Installing SamiOS CLI tool..." 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' cat > "$MOUNT_POINT/usr/local/bin/samios" << 'SAMIOS_CLI'
#!/bin/bash #!/bin/bash
# SamiOS CLI - installed by samios-installer.sh
# No embedded VERSION literal: walks up to /etc/samios-version at runtime.
set -e 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() { cmd_help() {
cat << EOF cat << EOF
@@ -340,6 +388,19 @@ esac
SAMIOS_CLI SAMIOS_CLI
chmod +x "$MOUNT_POINT/usr/local/bin/samios" 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 # MOTD
cat > "$MOUNT_POINT/etc/motd" << '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. Listens for voice commands from SamiType, routes them to Hermes or dictation mode.
Usage: Usage:
python3 voice-bridge.py python3 voice-bridge.py [--version]
Requires: Requires:
- SamiType STT running in streaming mode (Unix socket or WebSocket) - SamiType STT running in streaming mode (Unix socket or WebSocket)
@@ -21,6 +21,33 @@ import time
import threading import threading
from enum import Enum 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): class VoiceMode(Enum):
IDLE = "idle" IDLE = "idle"
COMMAND = "command" COMMAND = "command"
@@ -59,6 +86,7 @@ class VoiceBridge:
def start(self): def start(self):
"""Start the voice bridge""" """Start the voice bridge"""
self.running = True self.running = True
print(f"[VoiceBridge] SamiOS v{VERSION}")
print("[VoiceBridge] Ready. Say 'Computer' + command, or use hotkey.") print("[VoiceBridge] Ready. Say 'Computer' + command, or use hotkey.")
# Connect to SamiType STT # Connect to SamiType STT
@@ -199,6 +227,9 @@ class VoiceBridge:
return None return None
def main(): def main():
if "--version" in sys.argv or "-V" in sys.argv:
print(f"SamiOS voice-bridge v{VERSION}")
sys.exit(0)
bridge = VoiceBridge() bridge = VoiceBridge()
try: try:
bridge.start() bridge.start()
+1
View File
@@ -24,6 +24,7 @@ TEST_FILES=(
"test_pacman_conf.sh" "test_pacman_conf.sh"
"test_samios_cli.sh" "test_samios_cli.sh"
"test_font_policy.sh" "test_font_policy.sh"
"test_version.sh"
) )
TOTAL_RUN=0 TOTAL_RUN=0
+13 -1
View File
@@ -2,7 +2,13 @@
# Common test helpers for SamiOS test suite # Common test helpers for SamiOS test suite
# Sourced by individual test scripts # 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 # 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)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PROFILE_DIR="$REPO_ROOT/packaging/archiso" PROFILE_DIR="$REPO_ROOT/packaging/archiso"
SCRIPTS_DIR="$REPO_ROOT/packaging/scripts" 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 # 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