diff --git a/README.md b/README.md index f35528f..4e3685b 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,20 @@ release images. Its generated database credentials are random and exist only for that run. The exact database volume, networks, temporary source snapshot, and one-run images are removed automatically. +For a final or reviewable local run, use the recorded wrapper from a clean +Git worktree: + +```bash +./scripts/quality-record.sh final-audit +``` + +It runs the same isolated gate and writes `quality.log` plus `summary.txt` +under `output/regression/final-audit/`. The run directory is private to the +current user, the files are mode `0600`, and the summary records the exact Git +commit/tree, tool versions, exit codes, test result, and log SHA-256. The +generated `output/` tree remains excluded from Git because audit logs can +contain environment-specific diagnostic details. + The same external-service protocol drill used by CI can be run independently: ```bash diff --git a/docs/dependency-baseline.md b/docs/dependency-baseline.md index a707d8d..253b78e 100644 --- a/docs/dependency-baseline.md +++ b/docs/dependency-baseline.md @@ -149,6 +149,7 @@ docker run --rm who-need-help:node-deps npm --version docker run --rm who-need-help:node-deps npm outdated --json ./scripts/test.sh ./scripts/quality.sh +./scripts/quality-record.sh final-audit ./scripts/e2e-run.sh ./scripts/android-build.sh ./scripts/android-instrumentation-test.sh diff --git a/scripts/quality-record.sh b/scripts/quality-record.sh new file mode 100755 index 0000000..a1d671d --- /dev/null +++ b/scripts/quality-record.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +cd "$ROOT" + +umask 077 + +default_run_id="quality-$(date -u +%Y%m%dT%H%M%SZ)-$$" +run_id=${1:-$default_run_id} + +if [[ ! "$run_id" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then + echo "Run id must contain only letters, digits, dots, underscores, and hyphens." >&2 + exit 2 +fi + +output_root="$ROOT/output/regression" +output_dir="$output_root/$run_id" +quality_log="$output_dir/quality.log" +summary_file="$output_dir/summary.txt" + +if [[ -e "$output_dir" ]]; then + echo "Refusing to overwrite existing audit output: $output_dir" >&2 + exit 2 +fi + +if ! git diff --quiet || + ! git diff --cached --quiet || + [[ -n "$(git ls-files --others --exclude-standard)" ]]; then + echo "A recorded audit must identify one exact committed source state." >&2 + echo "Commit or remove the current tracked/untracked changes, then retry." >&2 + exit 2 +fi + +mkdir -p "$output_root" +mkdir -m 700 "$output_dir" +install -m 600 /dev/null "$quality_log" + +started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) +git_commit=$(git rev-parse HEAD) +git_tree=$(git rev-parse 'HEAD^{tree}') +docker_server_version=$(docker version --format '{{.Server.Version}}' 2>/dev/null || printf 'unavailable') +docker_compose_version=$(docker compose version --short 2>/dev/null || printf 'unavailable') + +set +e +./scripts/quality.sh 2>&1 | tee "$quality_log" +pipeline_status=("${PIPESTATUS[@]}") +set -e + +quality_exit_code=${pipeline_status[0]} +tee_exit_code=${pipeline_status[1]} +finished_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) +quality_log_sha256=$(sha256sum "$quality_log" | awk '{print $1}') +test_result=$(grep -E '^Result: [0-9]+ passed' "$quality_log" | tail -n 1 || true) + +status=failed +recorder_exit_code=1 +if [[ "$quality_exit_code" -eq 0 && + "$tee_exit_code" -eq 0 ]] && + grep -Fqx 'All isolated quality and security gates passed.' "$quality_log"; then + status=passed + recorder_exit_code=0 +fi + +summary_tmp="$output_dir/.summary.txt.tmp" +{ + printf 'run_id=%s\n' "$run_id" + printf 'started_at=%s\n' "$started_at" + printf 'finished_at=%s\n' "$finished_at" + printf 'git_commit=%s\n' "$git_commit" + printf 'git_tree=%s\n' "$git_tree" + printf 'git_status=clean\n' + printf 'docker_server_version=%s\n' "$docker_server_version" + printf 'docker_compose_version=%s\n' "$docker_compose_version" + printf 'quality_exit_code=%s\n' "$quality_exit_code" + printf 'tee_exit_code=%s\n' "$tee_exit_code" + printf 'status=%s\n' "$status" + printf 'test_result=%s\n' "$test_result" + printf 'quality_log_sha256=%s\n' "$quality_log_sha256" +} >"$summary_tmp" +chmod 600 "$summary_tmp" +mv "$summary_tmp" "$summary_file" + +printf '\nRecorded quality audit: %s\n' "$output_dir" +printf 'Status: %s\n' "$status" +printf 'Log SHA-256: %s\n' "$quality_log_sha256" + +exit "$recorder_exit_code"