Closer/scripts/verify-learnings-update.sh

119 lines
3.1 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Hardened LEARNINGS update verification helper.
#
# Previous verification gates used grep-only checks and could pass by matching
# an old file title (e.g. "Batch 6" appearing in the existing title). This helper
# snapshots the file mtime before a batch and checks it after, then verifies a
# fresh "Batch N" header appears in the first 5 lines.
#
# Usage:
# scripts/verify-learnings-update.sh snapshot <agent-name> <baseline-epoch>
# scripts/verify-learnings-update.sh check <agent-name> <batch-number>
#
# Conventions:
# LEARNINGS path: .learnings/<agent-name>/LEARNINGS.md
# Baseline store: .learnings/<agent-name>/.mtime-baseline
#
# Exit codes:
# 0 OK (file modified, batch header found)
# 1 FAIL (mtime unchanged or batch header missing)
# 2 WARNING / no baseline snapshot (needs manual review)
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
usage() {
cat <<EOF
Usage: $0 snapshot <agent-name> <baseline-epoch>
$0 check <agent-name> <batch-number>
snapshot Capture the current mtime of .learnings/<agent-name>/LEARNINGS.md
and write it to .learnings/<agent-name>/.mtime-baseline.
check Compare current mtime against the stored baseline and verify that
a "Batch N" header appears in the first 5 lines.
EOF
}
log() {
echo "[verify-learnings] $1"
}
main() {
if [[ $# -lt 2 ]]; then
usage
exit 2
fi
local mode="$1"
local agent="$2"
local learnings="$PROJECT_ROOT/.learnings/$agent/LEARNINGS.md"
local baseline="$PROJECT_ROOT/.learnings/$agent/.mtime-baseline"
if [[ ! -f "$learnings" ]]; then
log "ERROR: $learnings not found"
exit 2
fi
case "$mode" in
snapshot)
if [[ $# -lt 3 ]]; then
usage
exit 2
fi
local epoch="$3"
mkdir -p "$(dirname "$baseline")"
echo "$epoch" > "$baseline"
log "Snapshot for $agent: baseline epoch = $epoch"
exit 0
;;
check)
if [[ $# -lt 3 ]]; then
usage
exit 2
fi
local batch="$3"
if [[ ! -f "$baseline" ]]; then
log "ERROR: no baseline snapshot for $agent — run snapshot mode first."
exit 2
fi
local base_epoch
base_epoch="$(cat "$baseline")"
local cur_epoch
cur_epoch="$(stat -c '%Y' "$learnings")"
if [[ "$cur_epoch" -lt "$base_epoch" ]]; then
log "WARNING: mtime regressed for $agent (baseline $base_epoch -> current $cur_epoch); manual review needed."
exit 2
fi
if [[ "$cur_epoch" -eq "$base_epoch" ]]; then
log "FAIL: $agent LEARNINGS untouched (epoch unchanged at $cur_epoch)"
exit 1
fi
log "OK: $agent LEARNINGS updated (epoch: $base_epoch -> $cur_epoch)"
if ! head -n 5 "$learnings" | grep -qE "^## Batch $batch|^### Batch $batch"; then
log "FAIL: mtime updated but no Batch $batch header found in first 5 lines of $learnings"
exit 1
fi
log "OK: Batch $batch header present in first 5 lines"
exit 0
;;
*)
usage
exit 2
;;
esac
}
main "$@"