119 lines
3.1 KiB
Bash
Executable File
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 "$@"
|