#!/usr/bin/env bash set -euo pipefail source "$(dirname "$0")/lib/common.sh" ROOT="$(vmcc_root)" CMD="${1:-}" RUN_ID="${VMCC_RUN_ID:-$(run_id)}" DAY="$(date -u "+%Y-%m-%d")" EVID_DIR="$ROOT/30-evidence/$DAY/$RUN_ID" RULE_DIR="$ROOT/50-reports/$DAY/$RUN_ID/rules" REP_DIR="$ROOT/50-reports/$DAY/$RUN_ID" mkdir -p "$EVID_DIR" "$RULE_DIR" "$REP_DIR" hash_file() { local file="$1" if command -v sha256sum >/dev/null 2>&1; then sha256sum "$file" | awk '{print $1}' else shasum -a 256 "$file" | awk '{print $1}' fi } write_manifest() { require_cmd jq local manifest="$EVID_DIR/manifest.json" local ts ts="$(iso_utc_now)" if sort -z --version >/dev/null 2>&1; then find "$EVID_DIR" -type f ! -name "manifest.json" -print0 \ | LC_ALL=C sort -z \ | while IFS= read -r -d '' file; do local rel rel="${file#$ROOT/}" local sha sha="$(hash_file "$file")" jq -n --arg path "$rel" --arg sha "$sha" '{path:$path, sha256:$sha}' done \ | jq -s --arg ts "$ts" --arg run "$RUN_ID" '{ version: "1.0.0", collected_at: $ts, run_id: $run, files: . }' > "$manifest" else find "$EVID_DIR" -type f ! -name "manifest.json" \ | LC_ALL=C sort \ | while IFS= read -r file; do local rel rel="${file#$ROOT/}" local sha sha="$(hash_file "$file")" jq -n --arg path "$rel" --arg sha "$sha" '{path:$path, sha256:$sha}' done \ | jq -s --arg ts "$ts" --arg run "$RUN_ID" '{ version: "1.0.0", collected_at: $ts, run_id: $run, files: . }' > "$manifest" fi } run_collect() { echo "[vmcc] run_id=$RUN_ID" echo "[vmcc] collecting evidence -> $EVID_DIR" "$ROOT/20-collectors/collect_ledger_verify.sh" "$EVID_DIR" "$ROOT/20-collectors/collect_constitution_hash.sh" "$EVID_DIR" "$ROOT/20-collectors/collect_backup_restore_drill.sh" "$EVID_DIR" write_manifest } run_evaluate() { echo "[vmcc] evaluating rules -> $RULE_DIR" "$ROOT/40-rules/ledger_hash_chain_intact.sh" "$EVID_DIR" > "$RULE_DIR/ledger.hash_chain_intact.json" "$ROOT/40-rules/governance_constitution_pinned.sh" "$EVID_DIR" > "$RULE_DIR/governance.constitution_pinned.json" "$ROOT/40-rules/backup_restore_drill_recent.sh" "$EVID_DIR" > "$RULE_DIR/backup.restore_drill_recent.json" } run_report() { require_cmd jq echo "[vmcc] assembling report -> $REP_DIR/report.json" TS="$(iso_utc_now)" PASSED_COUNT="$(jq -s '[.[] | select(.passed==true)] | length' "$RULE_DIR"/*.json)" FAILED_COUNT="$(jq -s '[.[] | select(.passed==false)] | length' "$RULE_DIR"/*.json)" jq -n \ --arg version "1.0.0" \ --arg timestamp "$TS" \ --arg run_id "$RUN_ID" \ --arg day "$DAY" \ --slurpfile rules <(cat "$RULE_DIR"/*.json) \ --argjson passed "$PASSED_COUNT" \ --argjson failed "$FAILED_COUNT" \ '{ version: $version, timestamp: $timestamp, period: "run", run_id: $run_id, day: $day, summary: { rules_passed: $passed, rules_failed: $failed, status: (if $failed == 0 then "COMPLIANT" else "NONCOMPLIANT" end) }, rules: $rules }' > "$REP_DIR/report.json" } case "$CMD" in collect) run_collect ;; evaluate) run_evaluate ;; report) run_report ;; all) run_collect run_evaluate run_report if jq -e -s 'map(select(.passed == false)) | length > 0' "$RULE_DIR"/*.json >/dev/null; then exit 3 fi ;; *) echo "Usage: $0 {collect|evaluate|report|all}" >&2 exit 1 ;; esac