feat(P10b): drift detection (R-018/R-019/R-020, REQ-103..113)

internal/drift/drift.go: Detector (Watch via iter.Seq2, Aggregate,
Remediate with cooldown-on-success, Acknowledge), Config with tiered
cadence (critical 5s + Path units, standard 30s, default 60s).
internal/cli/drift.go: orca drift {show,watch,acknowledge,remediate,
config}. internal/emitter/drift_path.go: systemd Path+service unit
emitter (User=orca, ProtectSystem=strict). scripts/orca-drift-notify.sh
(sha256 event JSON), orca-remediate.sh (cooldown-on-success, transient
retry). Pre-flight gate (R-020, --force + per-ns scoping). orca
system user (REQ-111), NFS detection (D-233), orca job restart for
EnvironmentFile drift (D-235).

---ci---
project: orca
phase: 10b
milestone: v0.11
status: execute
---/ci---
This commit is contained in:
Jon Chery
2026-08-07 07:17:41 +00:00
parent 635e07e7a5
commit 03f3585f16
13 changed files with 2970 additions and 0 deletions
+55
View File
@@ -110,3 +110,58 @@ EOF
run "$PULL" --txn-dir "$TMP_TXN" --namespace default
assert_status 4 "$status"
}
@test "orca-pull.sh drift gate refuses with exit 6 when drift detected (R-020)" {
# Create a drift-events-aggregated.json with confirmed drift.
mkdir -p "$TMP_TXN/state"
DRIFT_JSON="$TMP_TXN/state/drift-events-aggregated.json"
cat >"$DRIFT_JSON" <<EOF
{"ts":"2026-01-01T00:00:00Z","events":[{"event_id":"E1","host":"p","path":"/etc/orca/actual/default/x","status":"modified","drift_confirmed":true}]}
EOF
ORCA_DRIFT_AGG_JSON="$DRIFT_JSON" run "$PULL" --txn-dir "$TMP_TXN" --namespace default
assert_status 6 "$status"
assert_contains "$output" "drift detected"
}
@test "orca-pull.sh drift gate passes when no drift" {
# Empty aggregated doc -> no drift -> applies.
mkdir -p "$TMP_TXN/state"
DRIFT_JSON="$TMP_TXN/state/drift-events-aggregated.json"
echo '{"ts":"2026-01-01T00:00:00Z","events":[]}' >"$DRIFT_JSON"
ORCA_DRIFT_AGG_JSON="$DRIFT_JSON" run "$PULL" --txn-dir "$TMP_TXN" --namespace default
assert_status 0 "$status"
}
@test "orca-pull.sh drift gate --force overrides (R-020)" {
# Create drift, then apply with --force --i-understand-the-risk.
mkdir -p "$TMP_TXN/state"
DRIFT_JSON="$TMP_TXN/state/drift-events-aggregated.json"
cat >"$DRIFT_JSON" <<EOF
{"ts":"2026-01-01T00:00:00Z","events":[{"event_id":"E1","host":"p","path":"/etc/orca/actual/default/x","status":"modified","drift_confirmed":true}]}
EOF
ORCA_DRIFT_AGG_JSON="$DRIFT_JSON" run "$PULL" --txn-dir "$TMP_TXN" --namespace default --force --i-understand-the-risk
assert_status 0 "$status"
}
@test "orca-pull.sh drift gate per-namespace scoping (ns-A drift does not block ns-B)" {
# Drift in ns-A; apply to ns-B should succeed.
mkdir -p "$TMP_TXN/state"
DRIFT_JSON="$TMP_TXN/state/drift-events-aggregated.json"
cat >"$DRIFT_JSON" <<EOF
{"ts":"2026-01-01T00:00:00Z","events":[{"event_id":"E1","host":"p","path":"/etc/orca/actual/ns-a/x","status":"modified","drift_confirmed":true}]}
EOF
ORCA_DRIFT_AGG_JSON="$DRIFT_JSON" run "$PULL" --txn-dir "$TMP_TXN" --namespace ns-b
assert_status 0 "$status"
}
@test "orca-pull.sh drift gate cluster-wide checks all confirmed drift" {
# No --namespace; cluster-wide requires --force. When --force given
# AND drift present, gate is skipped (force bypasses drift too).
mkdir -p "$TMP_TXN/state"
DRIFT_JSON="$TMP_TXN/state/drift-events-aggregated.json"
cat >"$DRIFT_JSON" <<EOF
{"ts":"2026-01-01T00:00:00Z","events":[{"event_id":"E1","host":"p","path":"/etc/traefik/dynamic/orca.yml","status":"modified","drift_confirmed":true}]}
EOF
ORCA_DRIFT_AGG_JSON="$DRIFT_JSON" run "$PULL" --txn-dir "$TMP_TXN" --force --i-understand-the-risk
assert_status 0 "$status"
}