Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -164,7 +166,14 @@ 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 --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 \
docker.io/library/bash@sha256:69d156705ff4829e60cd958dd356e8db024195efcdb0504eb3426c84647c6e88 \
bash examples/reference-apps/verify.sh

- name: Verify deterministic release invariants
run: ./tests/release-invariants.sh
15 changes: 15 additions & 0 deletions examples/reference-apps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
48 changes: 48 additions & 0 deletions examples/reference-apps/release-evidence.yaml
Original file line number Diff line number Diff line change
@@ -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
107 changes: 107 additions & 0 deletions examples/reference-apps/release-rehearsal.sh
Original file line number Diff line number Diff line change
@@ -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'
22 changes: 12 additions & 10 deletions tests/compatibility-matrix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ 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
fi
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')"
}
Expand All @@ -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
1 change: 1 addition & 0 deletions tests/lint-warnings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions tests/reference-apps.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
47 changes: 47 additions & 0 deletions tests/reference-release-contract.sh
Original file line number Diff line number Diff line change
@@ -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"
12 changes: 8 additions & 4 deletions tests/release-invariants.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
6 changes: 6 additions & 0 deletions tests/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 \
Expand All @@ -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 \
Expand Down Expand Up @@ -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 $?
Expand Down
Loading