mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
fc16cf31df
## Summary - add eval fast, eval baseline-audit, VIL, and digital-twin evidence to local release artifact manifests - require resolver/audit validation to reject incomplete SBOM/security/eval/HIL/VIL/twin proof bundles - document the zero-trust release proof bundle and add focused BATS coverage ## Validation - bash -n scripts/ci-local-release.sh scripts/resolve-release-artifacts.sh scripts/validate-release-audit-artifacts.sh - shellcheck --severity=error scripts/ci-local-release.sh scripts/resolve-release-artifacts.sh scripts/validate-release-audit-artifacts.sh - bats tests/scripts/ci-local-release.bats tests/scripts/release-artifacts.bats - git diff --check - markdownlint docs/RELEASING.md docs/release-e2e-checklist.md docs/contracts/release-readiness.md - bash scripts/validate-ci-policy-parity.sh - bash tests/docs/validate-doc-release.sh - scripts/pre-push-gate.sh --fast (passed; warn-only executable-spec link-integrity reports unknown flags --lint/--orphans) - bd dep cycles - scripts/bd-audit.sh --json Bead: soc-owed.7 --------- Co-authored-by: Codex <codex@example.invalid>
340 lines
10 KiB
Bash
Executable File
340 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
OUT=""
|
|
REQUIRED=false
|
|
WAIVER="${AGENTOPS_RELEASE_HIL_WAIVER:-}"
|
|
TIMEOUT_SECONDS="${AGENTOPS_RELEASE_HIL_TIMEOUT:-30}"
|
|
ENV_TARGETS="${AGENTOPS_RELEASE_HIL_TARGETS:-}"
|
|
EXPECTED_VERSION="${AGENTOPS_RELEASE_VERSION:-}"
|
|
TARGETS=()
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: scripts/check-release-hil.sh [options]
|
|
|
|
Capture release HIL evidence from real targets.
|
|
|
|
Options:
|
|
--out PATH Write JSON evidence to PATH
|
|
--target SPEC Add a target; repeatable
|
|
local:<name>:<command>
|
|
ssh:<name>:<host>:<command>
|
|
--targets TEXT Newline-separated target specs
|
|
--required Fail when no target is available unless --waiver is set
|
|
--waiver TEXT Record an explicit HIL waiver
|
|
--expected-version V
|
|
Require target output to mention this release version
|
|
--timeout SECONDS Per-target timeout when timeout(1) is available (default: 30)
|
|
-h, --help Show this help
|
|
|
|
AGENTOPS_RELEASE_HIL_TARGETS may also provide newline-separated target specs.
|
|
USAGE
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--out)
|
|
OUT="${2:-}"
|
|
shift 2
|
|
;;
|
|
--target)
|
|
TARGETS+=("${2:-}")
|
|
shift 2
|
|
;;
|
|
--targets)
|
|
ENV_TARGETS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--required)
|
|
REQUIRED=true
|
|
shift
|
|
;;
|
|
--waiver)
|
|
WAIVER="${2:-}"
|
|
shift 2
|
|
;;
|
|
--expected-version)
|
|
EXPECTED_VERSION="${2:-}"
|
|
shift 2
|
|
;;
|
|
--timeout)
|
|
TIMEOUT_SECONDS="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
echo "jq is required for release HIL evidence" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! "$TIMEOUT_SECONDS" =~ ^[0-9]+$ || "$TIMEOUT_SECONDS" -eq 0 ]]; then
|
|
echo "--timeout must be a positive integer" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "$ENV_TARGETS" ]]; then
|
|
while IFS= read -r target_spec; do
|
|
[[ -n "$target_spec" ]] && TARGETS+=("$target_spec")
|
|
done <<<"$ENV_TARGETS"
|
|
fi
|
|
|
|
timestamp() {
|
|
date -u +%Y-%m-%dT%H:%M:%SZ
|
|
}
|
|
|
|
json_array_add() {
|
|
local current_json="$1"
|
|
local value="$2"
|
|
|
|
jq -c --arg value "$value" '. + [$value]' <<<"$current_json"
|
|
}
|
|
|
|
command_sha256() {
|
|
local command_string="$1"
|
|
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
printf '%s' "$command_string" | sha256sum | awk '{print $1}'
|
|
else
|
|
printf '%s' "$command_string" | shasum -a 256 | awk '{print $1}'
|
|
fi
|
|
}
|
|
|
|
detect_workflow_checks() {
|
|
local command_string="$1"
|
|
local checks='[]'
|
|
|
|
[[ "$command_string" == *"ao version"* ]] && checks="$(json_array_add "$checks" "ao-version")"
|
|
[[ "$command_string" == *"ao init"* ]] && checks="$(json_array_add "$checks" "ao-init")"
|
|
[[ "$command_string" == *"ao hooks"* ]] && checks="$(json_array_add "$checks" "ao-hooks")"
|
|
[[ "$command_string" == *"ao rpi"* ]] && checks="$(json_array_add "$checks" "ao-rpi")"
|
|
[[ "$command_string" == *" install"* || "$command_string" == install* ]] && checks="$(json_array_add "$checks" "install")"
|
|
[[ "$command_string" == *" upgrade"* || "$command_string" == upgrade* ]] && checks="$(json_array_add "$checks" "upgrade")"
|
|
|
|
printf '%s\n' "$checks"
|
|
}
|
|
|
|
local_runtime_identity() {
|
|
jq -n \
|
|
--arg os "$(uname -s 2>/dev/null || true)" \
|
|
--arg arch "$(uname -m 2>/dev/null || true)" \
|
|
--arg kernel "$(uname -r 2>/dev/null || true)" \
|
|
--arg hostname "$(hostname 2>/dev/null || true)" \
|
|
'{os: $os, arch: $arch, kernel: $kernel, hostname: $hostname}'
|
|
}
|
|
|
|
remote_runtime_identity() {
|
|
local host="$1"
|
|
jq -n --arg host "$host" '{host: $host, runtime_identity: "remote-command"}'
|
|
}
|
|
|
|
run_local_target() {
|
|
local cmd_string="$1"
|
|
if command -v timeout >/dev/null 2>&1; then
|
|
timeout "$TIMEOUT_SECONDS" bash -lc "$cmd_string"
|
|
else
|
|
bash -lc "$cmd_string"
|
|
fi
|
|
}
|
|
|
|
run_ssh_target() {
|
|
local host="$1"
|
|
local cmd_string="$2"
|
|
if command -v timeout >/dev/null 2>&1; then
|
|
timeout "$TIMEOUT_SECONDS" ssh -o BatchMode=yes -o ConnectTimeout=10 "$host" "$cmd_string"
|
|
else
|
|
ssh -o BatchMode=yes -o ConnectTimeout=10 "$host" "$cmd_string"
|
|
fi
|
|
}
|
|
|
|
append_target_result() {
|
|
local current_json="$1"
|
|
local name="$2"
|
|
local kind="$3"
|
|
local host="$4"
|
|
local status="$5"
|
|
local exit_code="$6"
|
|
local duration_seconds="$7"
|
|
local output_preview="$8"
|
|
local command_hash="$9"
|
|
local workflow_checks="${10}"
|
|
local workflow_strength="${11}"
|
|
local expected_version="${12}"
|
|
local version_verified="${13}"
|
|
local runtime_identity="${14}"
|
|
local failure_reasons="${15}"
|
|
|
|
jq -c \
|
|
--arg name "$name" \
|
|
--arg kind "$kind" \
|
|
--arg host "$host" \
|
|
--arg status "$status" \
|
|
--arg output_preview "$output_preview" \
|
|
--arg command_sha256 "$command_hash" \
|
|
--arg workflow_strength "$workflow_strength" \
|
|
--arg expected_version "$expected_version" \
|
|
--arg version_verified "$version_verified" \
|
|
--argjson exit_code "$exit_code" \
|
|
--argjson duration_seconds "$duration_seconds" \
|
|
--argjson workflow_checks "$workflow_checks" \
|
|
--argjson runtime_identity "$runtime_identity" \
|
|
--argjson failure_reasons "$failure_reasons" \
|
|
'. + [{
|
|
name: $name,
|
|
kind: $kind,
|
|
host: (if $host == "" then null else $host end),
|
|
status: $status,
|
|
exit_code: $exit_code,
|
|
duration_seconds: $duration_seconds,
|
|
command_sha256: $command_sha256,
|
|
workflow_strength: $workflow_strength,
|
|
workflow_checks: $workflow_checks,
|
|
expected_version: (if $expected_version == "" then null else $expected_version end),
|
|
version_verified: (if $expected_version == "" then null else ($version_verified == "true") end),
|
|
runtime: $runtime_identity,
|
|
failure_reasons: $failure_reasons,
|
|
output_preview: $output_preview
|
|
}]' <<<"$current_json"
|
|
}
|
|
|
|
TARGET_RESULTS='[]'
|
|
FAILURES=0
|
|
|
|
if [[ "${#TARGETS[@]}" -eq 0 ]]; then
|
|
if [[ "$REQUIRED" == "true" && -z "$WAIVER" ]]; then
|
|
STATUS="fail"
|
|
EXIT_CODE=1
|
|
elif [[ "$REQUIRED" == "true" ]]; then
|
|
STATUS="waived"
|
|
EXIT_CODE=0
|
|
else
|
|
STATUS="skipped"
|
|
EXIT_CODE=0
|
|
fi
|
|
else
|
|
STATUS="pass"
|
|
EXIT_CODE=0
|
|
for target_spec in "${TARGETS[@]}"; do
|
|
started_epoch="$(date +%s)"
|
|
target_name=""
|
|
target_kind=""
|
|
target_host=""
|
|
target_command=""
|
|
tmp_output="$(mktemp)"
|
|
target_rc=0
|
|
failure_reasons='[]'
|
|
runtime_identity='{}'
|
|
|
|
if [[ "$target_spec" == local:*:* ]]; then
|
|
target_kind="local"
|
|
rest="${target_spec#local:}"
|
|
target_name="${rest%%:*}"
|
|
target_command="${rest#*:}"
|
|
runtime_identity="$(local_runtime_identity)"
|
|
set +e
|
|
run_local_target "$target_command" >"$tmp_output" 2>&1
|
|
target_rc=$?
|
|
set -e
|
|
elif [[ "$target_spec" == ssh:*:*:* ]]; then
|
|
target_kind="ssh"
|
|
rest="${target_spec#ssh:}"
|
|
target_name="${rest%%:*}"
|
|
rest="${rest#*:}"
|
|
target_host="${rest%%:*}"
|
|
target_command="${rest#*:}"
|
|
runtime_identity="$(remote_runtime_identity "$target_host")"
|
|
set +e
|
|
run_ssh_target "$target_host" "$target_command" >"$tmp_output" 2>&1
|
|
target_rc=$?
|
|
set -e
|
|
else
|
|
target_kind="invalid"
|
|
target_name="${target_spec%%:*}"
|
|
printf 'invalid target spec: %s\n' "$target_spec" >"$tmp_output"
|
|
target_rc=2
|
|
fi
|
|
|
|
duration_seconds=$(( $(date +%s) - started_epoch ))
|
|
output_preview="$(tail -20 "$tmp_output")"
|
|
rm -f "$tmp_output"
|
|
|
|
workflow_checks="$(detect_workflow_checks "$target_command")"
|
|
workflow_count="$(jq -r 'length' <<<"$workflow_checks")"
|
|
workflow_strength="strong"
|
|
if [[ "$workflow_count" -lt 2 ]]; then
|
|
workflow_strength="weak"
|
|
fi
|
|
|
|
version_verified=false
|
|
if [[ -n "$EXPECTED_VERSION" ]]; then
|
|
if grep -Fq "$EXPECTED_VERSION" <<<"$output_preview"; then
|
|
version_verified=true
|
|
else
|
|
failure_reasons="$(json_array_add "$failure_reasons" "version_mismatch")"
|
|
fi
|
|
fi
|
|
|
|
target_status="pass"
|
|
if [[ "$target_rc" -ne 0 ]]; then
|
|
failure_reasons="$(json_array_add "$failure_reasons" "command_failed")"
|
|
fi
|
|
if [[ "$REQUIRED" == "true" && "$workflow_strength" == "weak" ]]; then
|
|
failure_reasons="$(json_array_add "$failure_reasons" "weak_workflow")"
|
|
fi
|
|
if [[ "$(jq -r 'length' <<<"$failure_reasons")" -gt 0 ]]; then
|
|
target_status="fail"
|
|
FAILURES=$((FAILURES + 1))
|
|
STATUS="fail"
|
|
EXIT_CODE=1
|
|
fi
|
|
|
|
TARGET_RESULTS="$(append_target_result \
|
|
"$TARGET_RESULTS" "$target_name" "$target_kind" "$target_host" \
|
|
"$target_status" "$target_rc" "$duration_seconds" "$output_preview" \
|
|
"$(command_sha256 "$target_command")" "$workflow_checks" "$workflow_strength" \
|
|
"$EXPECTED_VERSION" "$version_verified" "$runtime_identity" "$failure_reasons")"
|
|
done
|
|
fi
|
|
|
|
REQUIRED_JSON=false
|
|
[[ "$REQUIRED" == "true" ]] && REQUIRED_JSON=true
|
|
|
|
DOCUMENT="$(jq -n \
|
|
--arg generated_at "$(timestamp)" \
|
|
--arg status "$STATUS" \
|
|
--arg waiver "$WAIVER" \
|
|
--arg expected_version "$EXPECTED_VERSION" \
|
|
--argjson required "$REQUIRED_JSON" \
|
|
--argjson timeout_seconds "$TIMEOUT_SECONDS" \
|
|
--argjson targets "$TARGET_RESULTS" \
|
|
'{
|
|
schema_version: 1,
|
|
generated_at: $generated_at,
|
|
status: $status,
|
|
required: $required,
|
|
waiver: (if $waiver == "" then null else $waiver end),
|
|
expected_version: (if $expected_version == "" then null else $expected_version end),
|
|
timeout_seconds: $timeout_seconds,
|
|
targets: $targets
|
|
}')"
|
|
|
|
if [[ -n "$OUT" ]]; then
|
|
mkdir -p "$(dirname "$OUT")"
|
|
printf '%s\n' "$DOCUMENT" >"$OUT"
|
|
fi
|
|
|
|
printf '%s\n' "$DOCUMENT"
|
|
|
|
exit "$EXIT_CODE"
|