#!/usr/bin/env bash # ci/lib.sh — shared helpers for the merge-gate scripts. # Sourced by every ci/*.sh; never executed directly. # # Conventions: # - All scripts: `set -euo pipefail`. # - Log to stderr; machine-readable output (e.g. requirement lists) to stdout. # - Exit codes: 0 = pass, 1 = fail (gate blocks), 3 = SKIPPED/not-run # (cluster-gated stage with no cluster — NOT a pass, NOT a fail). set -euo pipefail # --- Paths ------------------------------------------------------------------- # CI_DIR = this ci/ directory (always the real script location). REPO_ROOT = its # parent (impl repo root) by default, but OVERRIDABLE via the environment so the # scripts can be pointed at a throwaway git repo under test (see ci/tests/) without # copying them out of the tree. CI and normal runs leave it unset -> unchanged. CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" : "${REPO_ROOT:=$(cd "${CI_DIR}/.." && pwd)}" # shellcheck disable=SC1091 source "${CI_DIR}/versions.env" # --- Integration points (other agents' entrypoints) -------------------------- # These are the seams this gate depends on. Each has an env override so the PM / # owning agent can reconcile the real entrypoint at integration WITHOUT editing # these scripts. Defaults are this workstream's *assumptions* — see README. : "${CATALOG_DIR:=${REPO_ROOT}/catalog}" # ServiceDefinition sources : "${SCHEMAS_DIR:=${REPO_ROOT}/schemas}" # owned by T-0.3 (schema + renderer contract) : "${KYVERNO_POLICY_DIR:=${REPO_ROOT}/platform/kyverno}" # owned by T-0.5 (L0 Kyverno policies + tests) : "${CONFORMANCE_DIR:=${REPO_ROOT}/conformance}" # owned by T-0.4 (Chainsaw harness + coverage) # Commands (overridable). Defaults documented in ci/README.md. : "${SCHEMA_VALIDATE_CMD:=${SCHEMAS_DIR}/validate.sh}" # ASSUMED: validates catalog/*.yaml against the ServiceDefinition schema : "${CONFORMANCE_CMD:=${CONFORMANCE_DIR}/run.sh}" # ASSUMED: single-command conformance runner (R-TEST-02) # --- Artifact locations (git-ignored: see .gitignore) ------------------------ : "${RESULTS_DIR:=${REPO_ROOT}/results}" # JUnit XML lands here : "${REPORTS_DIR:=${REPO_ROOT}/reports}" # requirement-coverage report lands here : "${JUNIT_DIR:=${RESULTS_DIR}/junit}" : "${COVERAGE_DIR:=${REPORTS_DIR}/coverage}" # --- Logging ----------------------------------------------------------------- _is_tty() { [ -t 2 ]; } if _is_tty; then C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YEL=$'\033[33m'; C_BLU=$'\033[34m'; C_DIM=$'\033[2m'; C_RST=$'\033[0m' else C_RED=""; C_GRN=""; C_YEL=""; C_BLU=""; C_DIM=""; C_RST=""; fi log() { printf '%s[gate]%s %s\n' "$C_BLU" "$C_RST" "$*" >&2; } ok() { printf '%s[ ok ]%s %s\n' "$C_GRN" "$C_RST" "$*" >&2; } warn() { printf '%s[warn]%s %s\n' "$C_YEL" "$C_RST" "$*" >&2; } err() { printf '%s[fail]%s %s\n' "$C_RED" "$C_RST" "$*" >&2; } skip() { printf '%s[skip]%s %s\n' "$C_YEL" "$C_RST" "$*" >&2; } die() { err "$*"; exit 1; } # Emit a GitHub/Gitea-Actions step-summary line if running in Actions. summary() { [ -n "${GITHUB_STEP_SUMMARY:-}" ] && printf '%s\n' "$*" >> "$GITHUB_STEP_SUMMARY" || true; } # --- Tooling ----------------------------------------------------------------- have() { command -v "$1" >/dev/null 2>&1; } # require_tool — fail with an actionable message if missing. require_tool() { have "$1" && return 0 die "required tool '$1' not found. $2" } # entrypoint_ready — returns 0 only if it exists AND is runnable. # Used to distinguish 'other agent hasn't delivered yet / non-executable stub' # (-> SKIP) from a wired entrypoint that genuinely fails (-> FAIL). A file that # exists but isn't executable counts as NOT ready (unwired), not a failure. entrypoint_ready() { local ep="$1" have "$ep" && return 0 # a bare command on PATH [ -f "$ep" ] && [ -x "$ep" ] && return 0 # an executable file return 1 } # --- Cluster detection (drives SKIP vs run for L2/L3) ------------------------- # A cluster is 'available' only if explicitly declared. We NEVER auto-assume a # cluster in Phase 0 — silence means SKIP, not pass (HARD RULE 4). # CLUSTER_AVAILABLE=true -> L2/L3 run and are REQUIRED green # CLUSTER_AVAILABLE=false/unset -> L2/L3 SKIP (reported, never faked green) cluster_available() { case "${CLUSTER_AVAILABLE:-false}" in true|1|yes) return 0 ;; *) return 1 ;; esac } mkartifacts() { mkdir -p "$JUNIT_DIR" "$COVERAGE_DIR"; }