From 914ba95b8bad34b0fc4033cacab9e194bbbe5227 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Thu, 6 Aug 2026 03:53:24 -0700 Subject: [PATCH 1/3] feat(reference-apps): add immutable release rehearsal (#238) --- .github/workflows/tests.yml | 10 +- examples/reference-apps/README.md | 15 +++ examples/reference-apps/release-evidence.yaml | 48 ++++++++ examples/reference-apps/release-rehearsal.sh | 107 ++++++++++++++++++ tests/lint-warnings.sh | 1 + tests/reference-apps.bats | 12 ++ tests/reference-release-contract.sh | 47 ++++++++ tests/validate.sh | 6 + 8 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 examples/reference-apps/release-evidence.yaml create mode 100755 examples/reference-apps/release-rehearsal.sh create mode 100755 tests/reference-release-contract.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 464bcb8..abeeb8e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -101,6 +101,7 @@ jobs: bash tests/property-contract.sh bash tests/artifact-contract.sh bash tests/benchmark-contract.sh + bash examples/reference-apps/verify.sh ' representative-bash: @@ -147,6 +148,7 @@ jobs: bash tests/artifact-contract.sh bash tests/benchmark-contract.sh bash tests/concurrency-contract.sh + bash examples/reference-apps/verify.sh ' release-gates: @@ -164,7 +166,13 @@ jobs: run: sudo apt-get install -y bats shellcheck - name: Run networkless platform matrix - run: ./tests/compatibility-matrix.sh --container alpine + run: | + ./tests/compatibility-matrix.sh --container alpine + docker run --rm --network none --read-only --cap-drop ALL \ + --tmpfs /tmp:rw,noexec,nosuid,nodev,size=16m,mode=1777 \ + --mount "type=bind,src=$GITHUB_WORKSPACE,dst=/workspace,readonly" \ + --workdir /workspace alpine:3.20 sh -c \ + 'apk add --no-cache bash >/dev/null && bash examples/reference-apps/verify.sh' - name: Verify deterministic release invariants run: ./tests/release-invariants.sh diff --git a/examples/reference-apps/README.md b/examples/reference-apps/README.md index 6d87acf..dbcf2ad 100644 --- a/examples/reference-apps/README.md +++ b/examples/reference-apps/README.md @@ -22,3 +22,18 @@ Each fixture has failure-path BATS coverage. The apps use only supported public APIs; optional network and GitHub operations are never performed by tests. Release inputs remain immutable pins and are verified by the repository bundle and vendor checks. + +The RC→GA and rollback rehearsal is explicit and networkless. Given two +independently verified unpacked framework roots, run: + +```bash +examples/reference-apps/release-rehearsal.sh \ + --candidate /path/to/v2-candidate \ + --rollback /path/to/previous-v2-release \ + --report /tmp/base-reference-release.tsv +``` + +The required evidence schema and platform matrix live in +[`release-evidence.yaml`](release-evidence.yaml). Placeholders remain marked +`pending-ga-asset` until the canonical v2 asset, checksum, and provenance are +published; the repository never treats a moving checkout as release evidence. diff --git a/examples/reference-apps/release-evidence.yaml b/examples/reference-apps/release-evidence.yaml new file mode 100644 index 0000000..21fda2f --- /dev/null +++ b/examples/reference-apps/release-evidence.yaml @@ -0,0 +1,48 @@ +schema_version: 1 +release_line: v2.0.0 +status: pending-ga-asset + +# Fill these fields only from the canonical release asset and its independently +# verified checksum. A local checkout or a moving branch is not acceptable. +candidate: + ref: pending-v2.0.0-rc-or-ga + commit: pending + sha256: pending +rollback: + ref: v1.4.0 + commit: 2c5ef2c3a9edfbe2cf68d0645be65b920255abff + sha256: historical-release-asset-checksum-required + +deployment_modes: + - source-checkout + - vendored-tree + - generated-bundle + - standalone-bundle + +platform_matrix: + - bash: 4.2.53 + os: linux + libc: glibc + evidence: required + - bash: current + os: linux + libc: glibc + evidence: required + - bash: current + os: macOS + libc: system + evidence: required + - bash: current + os: BSD-derived-userland + libc: system + evidence: advisory + - bash: current + os: linux + libc: musl + evidence: required + +rehearsal_command: examples/reference-apps/release-rehearsal.sh +benchmark_command: BASE_REFERENCE_BENCHMARK_ITERATIONS=2 benchmarks/reference-apps.sh +claims: + - no-external-production-use-claim + - no-unsupported-performance-claim diff --git a/examples/reference-apps/release-rehearsal.sh b/examples/reference-apps/release-rehearsal.sh new file mode 100755 index 0000000..d47ac52 --- /dev/null +++ b/examples/reference-apps/release-rehearsal.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash + +# Rehearse an immutable v2 deployment and a rollback without network access. +# The caller supplies unpacked, independently verified framework directories. + +rehearsal_repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)" || exit 1 +rehearsal_launcher="$rehearsal_repo_root/bin/base-bash" +rehearsal_report="" +rehearsal_candidate="" +rehearsal_rollback="" + +rehearsal_usage() { + cat >&2 << 'EOF' +Usage: examples/reference-apps/release-rehearsal.sh \ + --candidate FRAMEWORK_DIR --rollback FRAMEWORK_DIR [--report FILE] + +FRAMEWORK_DIR must be an unpacked, independently verified v2 framework root +containing lib/bash/base-bash-libs.release. The candidate is exercised first; +the rollback directory is then exercised as the previous immutable target. +EOF +} + +rehearsal_fail() { + printf 'Reference release rehearsal failed: %s\n' "$*" >&2 + exit 1 +} + +while (($#)); do + case "$1" in + --candidate) + [[ -n "${2-}" ]] || rehearsal_fail '--candidate requires a directory' + rehearsal_candidate="$2" + shift + ;; + --rollback) + [[ -n "${2-}" ]] || rehearsal_fail '--rollback requires a directory' + rehearsal_rollback="$2" + shift + ;; + --report) + [[ -n "${2-}" ]] || rehearsal_fail '--report requires a file' + rehearsal_report="$2" + shift + ;; + -h | --help) + rehearsal_usage + exit 0 + ;; + *) + rehearsal_usage + exit 2 + ;; + esac + shift +done + +[[ -n "$rehearsal_candidate" && -n "$rehearsal_rollback" ]] || { + rehearsal_usage + exit 2 +} + +rehearsal_candidate="$(cd -- "$rehearsal_candidate" 2> /dev/null && pwd -P)" || + rehearsal_fail "candidate directory is not accessible" +rehearsal_rollback="$(cd -- "$rehearsal_rollback" 2> /dev/null && pwd -P)" || + rehearsal_fail "rollback directory is not accessible" + +for rehearsal_root in "$rehearsal_candidate" "$rehearsal_rollback"; do + [[ -f "$rehearsal_root/lib/bash/base-bash-libs.release" ]] || + rehearsal_fail "framework metadata is missing under $rehearsal_root" +done + +if [[ -n "$rehearsal_report" ]]; then + rehearsal_report_dir="$(dirname -- "$rehearsal_report")" + [[ -d "$rehearsal_report_dir" ]] || rehearsal_fail "report directory is missing" + : > "$rehearsal_report" || rehearsal_fail "report is not writable" + printf 'schema_version=1\n' >> "$rehearsal_report" + printf 'bash=%s\n' "$BASH_VERSION" >> "$rehearsal_report" + printf 'os=%s\n' "$(uname -s)" >> "$rehearsal_report" +fi + +rehearsal_phase() { + local phase="$1" + local framework_root="$2" + local app app_command status + + for app in installer release-helper ops-cli; do + case "$app" in + installer | ops-cli) app_command=status ;; + release-helper) app_command=check ;; + esac + BASE_BASH_LIBS_DIR="$framework_root/lib/bash" \ + "$rehearsal_launcher" "$rehearsal_repo_root/examples/reference-apps/$app/bin/app" --help > /dev/null || + rehearsal_fail "$phase $app help failed" + BASE_BASH_LIBS_DIR="$framework_root/lib/bash" \ + "$rehearsal_launcher" "$rehearsal_repo_root/examples/reference-apps/$app/bin/app" "$app_command" > /dev/null 2>&1 + status=$? + [[ "$status" -eq 0 ]] || rehearsal_fail "$phase $app status failed with $status" + if [[ -n "$rehearsal_report" ]]; then + printf 'phase=%s\tapp=%s\tstatus=pass\n' "$phase" "$app" >> "$rehearsal_report" + fi + done +} + +rehearsal_phase candidate "$rehearsal_candidate" +rehearsal_phase rollback "$rehearsal_rollback" + +printf 'Reference release rehearsal passed: candidate and rollback apps=3.\n' diff --git a/tests/lint-warnings.sh b/tests/lint-warnings.sh index b509fde..dbff1c9 100755 --- a/tests/lint-warnings.sh +++ b/tests/lint-warnings.sh @@ -33,6 +33,7 @@ lint_files=( tests/property-contract.sh tests/benchmark-contract.sh tests/integration-release-contract.sh + tests/reference-release-contract.sh tests/concurrency-contract.sh tests/quality-contract.sh tests/shfmt-contract.sh diff --git a/tests/reference-apps.bats b/tests/reference-apps.bats index bb03a6b..d5a93eb 100644 --- a/tests/reference-apps.bats +++ b/tests/reference-apps.bats @@ -18,3 +18,15 @@ setup() { [ "$status" -eq 0 ] done } + +@test "reference applications rehearse candidate and rollback boundaries" { + run "$repo_root/examples/reference-apps/release-rehearsal.sh" \ + --candidate "$repo_root" \ + --rollback "$repo_root" \ + --report "$BATS_TEST_TMPDIR/reference-release.tsv" + + [ "$status" -eq 0 ] + [[ "$output" == *"candidate and rollback apps=3"* ]] + grep -F $'phase=candidate\tapp=installer\tstatus=pass' "$BATS_TEST_TMPDIR/reference-release.tsv" + grep -F $'phase=rollback\tapp=ops-cli\tstatus=pass' "$BATS_TEST_TMPDIR/reference-release.tsv" +} diff --git a/tests/reference-release-contract.sh b/tests/reference-release-contract.sh new file mode 100755 index 0000000..bc97baa --- /dev/null +++ b/tests/reference-release-contract.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +# Networkless contract for reference-app release evidence. It accepts the +# pre-GA pending state but prevents placeholders from being treated as release +# evidence. + +reference_release_repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)" || exit 1 +reference_release_evidence="$reference_release_repo_root/examples/reference-apps/release-evidence.yaml" +reference_release_fail() { + printf 'Reference release contract failed: %s\n' "$*" >&2 + exit 1 +} + +[[ -f "$reference_release_evidence" ]] || reference_release_fail 'evidence manifest is missing' +grep -Fx 'schema_version: 1' "$reference_release_evidence" > /dev/null || + reference_release_fail 'schema version is not 1' +grep -Fx 'release_line: v2.0.0' "$reference_release_evidence" > /dev/null || + reference_release_fail 'release line is not v2.0.0' +grep -Fx 'rehearsal_command: examples/reference-apps/release-rehearsal.sh' \ + "$reference_release_evidence" > /dev/null || reference_release_fail 'rehearsal command is missing' + +for app in installer release-helper ops-cli; do + [[ -f "$reference_release_repo_root/examples/reference-apps/$app/bin/app" ]] || + reference_release_fail "missing $app launcher" + [[ -f "$reference_release_repo_root/examples/reference-apps/$app/tests/app.bats" ]] || + reference_release_fail "missing $app failure suite" +done + +platform_count="$(grep -Ec '^ - bash: ' "$reference_release_evidence")" +[[ "$platform_count" == 5 ]] || reference_release_fail "expected five platform rows, found $platform_count" +grep -Fx ' - no-external-production-use-claim' "$reference_release_evidence" > /dev/null || + reference_release_fail 'evidence must prohibit an external-production claim' +grep -Fx ' - no-unsupported-performance-claim' "$reference_release_evidence" > /dev/null || + reference_release_fail 'evidence must prohibit an unsupported performance claim' + +if grep -E '^( ref| commit| sha256): pending' "$reference_release_evidence" > /dev/null; then + grep -Fx 'status: pending-ga-asset' "$reference_release_evidence" > /dev/null || + reference_release_fail 'placeholder references require pending-ga-asset status' + printf 'Reference release contract passed: status=pending-ga-asset platforms=%s.\n' "$platform_count" + exit 0 +fi + +grep -Fx 'status: verified' "$reference_release_evidence" > /dev/null || + reference_release_fail 'non-placeholder evidence must be marked verified' +grep -E '^ (commit|sha256): [[:xdigit:]]{40,64}$' "$reference_release_evidence" > /dev/null || + reference_release_fail 'verified evidence requires immutable identities' +printf 'Reference release contract passed: status=verified platforms=%s.\n' "$platform_count" diff --git a/tests/validate.sh b/tests/validate.sh index c22a145..31a2084 100755 --- a/tests/validate.sh +++ b/tests/validate.sh @@ -80,6 +80,8 @@ required_files=( examples/reference-apps/ops-cli/lib/app.sh examples/reference-apps/ops-cli/tests/app.bats examples/reference-apps/verify.sh + examples/reference-apps/release-rehearsal.sh + examples/reference-apps/release-evidence.yaml benchmarks/README.md benchmarks/reference-apps.sh tests/reference-apps.bats @@ -129,6 +131,7 @@ check_no_strict_mode() { tests/property-contract.sh tests/benchmark-contract.sh tests/integration-release-contract.sh + tests/reference-release-contract.sh tests/concurrency-contract.sh tests/quality-contract.sh tests/shfmt-contract.sh @@ -382,6 +385,7 @@ run_stage "ShellCheck error profile" shellcheck --severity=error \ tests/property-contract.sh \ tests/benchmark-contract.sh \ tests/integration-release-contract.sh \ + tests/reference-release-contract.sh \ tests/concurrency-contract.sh \ tests/quality-contract.sh \ tests/shfmt-contract.sh \ @@ -408,6 +412,7 @@ run_stage "ShellCheck error profile" shellcheck --severity=error \ examples/reference-apps/release-helper/lib/app.sh \ examples/reference-apps/ops-cli/lib/app.sh \ examples/reference-apps/verify.sh \ + examples/reference-apps/release-rehearsal.sh \ benchmarks/reference-apps.sh \ tests/reference-apps.bats \ tests/community-contract.sh \ @@ -440,6 +445,7 @@ run_stage "deterministic property contract" tests/property-contract.sh || exit $ run_stage "distribution artifact contract" tests/artifact-contract.sh || exit $? run_stage "benchmark contract" tests/benchmark-contract.sh || exit $? run_stage "integration release contract" tests/integration-release-contract.sh || exit $? +run_stage "reference release contract" tests/reference-release-contract.sh || exit $? run_stage "concurrency contract" tests/concurrency-contract.sh || exit $? run_stage "quality workflow contract" tests/quality-contract.sh || exit $? run_stage "support matrix" tests/compatibility-matrix.sh || exit $? From fb4c0f22f7b673b862db4d0aac4d21996db9bc1e Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Thu, 6 Aug 2026 03:59:16 -0700 Subject: [PATCH 2/3] fix(ci): pin Bash musl reference image (#238) --- .github/workflows/tests.yml | 7 ++++--- tests/compatibility-matrix.sh | 22 ++++++++++++---------- tests/release-invariants.sh | 12 ++++++++---- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index abeeb8e..ccc21fc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -168,11 +168,12 @@ jobs: - name: Run networkless platform matrix run: | ./tests/compatibility-matrix.sh --container alpine - docker run --rm --network none --read-only --cap-drop ALL \ + docker run --rm --platform linux/amd64 --network none --read-only --cap-drop ALL \ --tmpfs /tmp:rw,noexec,nosuid,nodev,size=16m,mode=1777 \ --mount "type=bind,src=$GITHUB_WORKSPACE,dst=/workspace,readonly" \ - --workdir /workspace alpine:3.20 sh -c \ - 'apk add --no-cache bash >/dev/null && bash examples/reference-apps/verify.sh' + --workdir /workspace \ + docker.io/library/bash@sha256:69d156705ff4829e60cd958dd356e8db024195efcdb0504eb3426c84647c6e88 \ + bash examples/reference-apps/verify.sh - name: Verify deterministic release invariants run: ./tests/release-invariants.sh diff --git a/tests/compatibility-matrix.sh b/tests/compatibility-matrix.sh index b787018..3ce3b31 100755 --- a/tests/compatibility-matrix.sh +++ b/tests/compatibility-matrix.sh @@ -22,12 +22,12 @@ matrix_probe_bash() { local matrix_file matrix_version matrix_major matrix_minor matrix_patch [[ -x "$matrix_bash" ]] || matrix_fail "Bash runtime is not executable: $matrix_bash" - matrix_version="$($matrix_bash -c 'printf "%s.%s.%s" "${BASH_VERSINFO[0]}" "${BASH_VERSINFO[1]}" "${BASH_VERSINFO[2]}"' 2>/dev/null)" || + matrix_version="$($matrix_bash -c 'printf "%s.%s.%s" "${BASH_VERSINFO[0]}" "${BASH_VERSINFO[1]}" "${BASH_VERSINFO[2]}"' 2> /dev/null)" || matrix_fail "unable to determine Bash version for $matrix_bash" - IFS=. read -r matrix_major matrix_minor matrix_patch <<<"$matrix_version" - if ((matrix_major < 4 || - (matrix_major == 4 && matrix_minor < 2) || - (matrix_major == 4 && matrix_minor == 2 && matrix_patch < 53))); then + IFS=. read -r matrix_major matrix_minor matrix_patch <<< "$matrix_version" + if ((matrix_major < 4 || (\ + matrix_major == 4 && matrix_minor < 2) || (\ + matrix_major == 4 && matrix_minor == 2 && matrix_patch < 53))); then printf 'SKIP bash=%s version=%s reason=below-minimum-4.2.53\n' \ "$matrix_bash" "$matrix_version" return 0 @@ -35,7 +35,7 @@ matrix_probe_bash() { for matrix_file in "${matrix_bash_files[@]}"; do "$matrix_bash" -n "$matrix_file" || matrix_fail "$matrix_bash failed syntax check for $matrix_file" done - "$matrix_bash" "$matrix_repo_root/tests/bash-option-contract.sh" >/dev/null 2>&1 || + "$matrix_bash" "$matrix_repo_root/tests/bash-option-contract.sh" > /dev/null 2>&1 || matrix_fail "$matrix_bash failed the option contract" printf 'PASS bash=%s version=%s\n' "$matrix_bash" "$($matrix_bash --version | sed -n '1p')" } @@ -50,13 +50,15 @@ done if [[ "${1:-}" == --container ]]; then [[ "${2:-}" == alpine ]] || matrix_fail "usage: $0 [--container alpine]" - if ! command -v docker >/dev/null 2>&1; then + if ! command -v docker > /dev/null 2>&1; then printf 'SKIP container=alpine reason=docker-unavailable\n' exit 0 fi - docker run --rm --network none --read-only --cap-drop ALL \ + matrix_alpine_bash_image='docker.io/library/bash@sha256:69d156705ff4829e60cd958dd356e8db024195efcdb0504eb3426c84647c6e88' + docker run --rm --platform linux/amd64 --network none --read-only --cap-drop ALL \ --tmpfs /tmp:rw,noexec,nosuid,nodev,size=16m,mode=1777 \ --mount "type=bind,src=$matrix_repo_root,dst=/workspace,readonly" \ - --workdir /workspace alpine:3.20 sh -c 'apk add --no-cache bash >/dev/null && bash tests/bash-option-contract.sh' - printf 'PASS container=alpine\n' + --workdir /workspace "$matrix_alpine_bash_image" \ + bash tests/bash-option-contract.sh + printf 'PASS container=alpine bash-image=%s\n' "$matrix_alpine_bash_image" fi diff --git a/tests/release-invariants.sh b/tests/release-invariants.sh index bd02aa7..628f97c 100755 --- a/tests/release-invariants.sh +++ b/tests/release-invariants.sh @@ -18,10 +18,10 @@ invariant_fail() { } cd "$invariant_repo_root" || invariant_fail "unable to enter repository" -scripts/api-manifest check >/dev/null || invariant_fail "API manifest check failed" -scripts/library-bundle check >/dev/null || invariant_fail "library bundle check failed" -scripts/library-bundle bundle "$invariant_tmp/bundle" >/dev/null || invariant_fail "bundle creation failed" -scripts/library-bundle verify "$invariant_tmp/bundle" >/dev/null || invariant_fail "bundle verification failed" +scripts/api-manifest check > /dev/null || invariant_fail "API manifest check failed" +scripts/library-bundle check > /dev/null || invariant_fail "library bundle check failed" +scripts/library-bundle bundle "$invariant_tmp/bundle" > /dev/null || invariant_fail "bundle creation failed" +scripts/library-bundle verify "$invariant_tmp/bundle" > /dev/null || invariant_fail "bundle verification failed" while IFS= read -r workflow; do [[ -n "$workflow" ]] || continue @@ -32,4 +32,8 @@ while IFS= read -r workflow; do done < <(grep -E '^[[:space:]]*-[[:space:]]*uses:[[:space:]]*[^#]+' "$workflow" || true) done < <(find .github/workflows -type f -name '*.yml' -print | sort) +grep -F 'docker.io/library/bash@sha256:69d156705ff4829e60cd958dd356e8db024195efcdb0504eb3426c84647c6e88' \ + tests/compatibility-matrix.sh > /dev/null || + invariant_fail 'Alpine/musl Bash image is not immutable-pinned' + printf 'Release invariants passed; deterministic bundle verified at %s.\n' "$invariant_tmp/bundle" From 4916081f58d8dda06e4f4357d794adef7d45f413 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:34:58 -0700 Subject: [PATCH 3/3] ci: refresh protected check contexts