@@ -9,6 +9,213 @@ The format is based on the regulated environment requirements:
99
1010---
1111
12+ ## [ 2026-05-02 23:30] - Emergency-reclaim roadmap closeout: drain stopwatch + loop protection + integration tests
13+
14+ ** Author:** Erick Bourgeois
15+
16+ ### Added
17+ - ` src/loop_protection.rs ` (~ 75 LOC) + ` src/loop_protection_tests.rs `
18+ (14 pure-helper tests). Module exposes ` prune_old_reclaim_events ` ,
19+ ` record_emergency_reclaim_event ` , and ` should_warn_rapid_re_reclaim `
20+ — all operate on ` &mut VecDeque<DateTime<Utc>> ` so the tests drive
21+ every branch with no clock or controller. Threshold + window come
22+ from new constants in ` src/constants.rs `
23+ (` RAPID_RE_RECLAIM_THRESHOLD = 3 ` ,
24+ ` RAPID_RE_RECLAIM_WINDOW_SECS = 600 ` ,
25+ ` RAPID_RE_RECLAIM_MAX_TRACKED = 10 ` ,
26+ ` REASON_RAPID_RE_RECLAIM = "RapidReReclaim" ` ).
27+ - `Context.recent_reclaims:
28+ Arc<Mutex<HashMap<String, VecDeque<DateTime<Utc >>>>>` — in-memory
29+ per-SM tracker, keyed by ` "namespace/name" ` . Documented choice:
30+ no CRD schema bump, no status-patch round trip; controller restart
31+ resets the count, which the metric still captures on a trend basis.
32+ - ` src/metrics.rs ` : three new metrics with helpers —
33+ ` fivespot_emergency_drain_duration_seconds ` (HistogramVec,
34+ outcome={success|timeout|error}, buckets sized for the 60 s drain
35+ ceiling), ` fivespot_emergency_reclaims_total ` (CounterVec by
36+ namespace+name), ` fivespot_rapid_re_reclaims_total ` (CounterVec by
37+ namespace+name). Helpers ` record_emergency_drain ` ,
38+ ` record_emergency_reclaim ` , ` record_rapid_re_reclaim ` plus
39+ ` EmergencyDrainOutcome ` enum. 4 new tests in ` metrics_tests.rs ` .
40+ - ` src/reconcilers/helpers.rs ` : drain call in ` handle_emergency_remove `
41+ is now wrapped in an ` Instant::now() ` stopwatch; outcome classified
42+ by error message (timeout substring → Timeout, other Err → Error,
43+ Ok → Success). Per-SM emergency-reclaim counter bumped once per
44+ flow (the idempotent recovery handler short-circuits before this
45+ point on restart). Loop-protection check fires after the bump:
46+ appends timestamp, prunes old entries, emits ` RapidReReclaim `
47+ Warning Event + bumps the new metric when threshold crosses. New
48+ pure helper ` build_rapid_re_reclaim_event(name) ` .
49+ - ` tests/integration_netlink_proc.rs ` — Linux-only (`#![ cfg(target_os
50+ = "linux")] `) runtime test for the netlink subscriber. Two
51+ ` #[ignore] ` cases: one targets a spawned ` /bin/true ` child and
52+ asserts <100 ms detection latency per issue #40 acceptance
53+ criterion; one is a quieter "subscriber sees * some* event"
54+ smoke test.
55+ - ` tests/integration_emergency_reclaim.rs ` — kind-cluster annotation
56+ contract test (graceful-skip if no cluster, ` #[ignore] ` ). Two cases:
57+ agent's ` build_patch_body ` round-trips through the controller's
58+ ` node_reclaim_request ` parser; partial annotations (reason set,
59+ requested=true absent) parse to ` None ` . Plus one always-on
60+ constants-pinning test.
61+
62+ ### Changed
63+ - ` src/reconcilers/mod.rs ` : re-export ` build_clear_reclaim_patch ` ,
64+ ` node_reclaim_request ` , and ` ReclaimRequest ` from ` helpers ` so the
65+ kind-cluster test can use the published API surface.
66+ - Roadmap ` ~/dev/roadmaps/5spot-emergency-reclaim-by-process-match.md `
67+ → renamed ` completed-5spot-emergency-reclaim-by-process-match.md `
68+ (per the user-memory rule "rename completed roadmaps with
69+ ` completed- ` prefix when every phase is done"). Status header
70+ rewritten to ✅ Complete; Phase 2.c rung 2 section flipped from
71+ "deferred to issue #40 " to fully shipped; deferred-items section
72+ rewritten as "Closed follow-ups" with the four checkboxes ticked.
73+
74+ ### Why
75+ Closes the four open items the rung 2 implementation called out as
76+ explicit follow-ups:
77+
78+ 1 . ** Drain stopwatch** — operators now have a Prometheus histogram of
79+ actual emergency-drain duration sliced by outcome, so SLO dashboards
80+ can compute success-only P95 vs timeout-rate side by side.
81+ 2 . ** Re-enable loop protection** — when a user re-enables a SM whose
82+ conflicting process is still running, the third reclaim within
83+ 10 minutes now triggers a ` RapidReReclaim ` Warning Event telling the
84+ operator to stop the conflicting process before re-enabling. The
85+ loop is no longer silent.
86+ 3 . ** Linux netlink runtime test** — issue #40 acceptance criterion
87+ "JVM launch produces a match within <100 ms" is now verifiable via
88+ ` cargo test --test integration_netlink_proc -- --ignored ` on a real
89+ Linux node with ` CAP_NET_ADMIN ` .
90+ 4 . ** Kind-cluster integration test** — proves the agent → controller
91+ annotation contract round-trips through a real API server. Catches
92+ the entire class of "agent writes annotations the controller can't
93+ parse" regressions that no unit test would surface.
94+
95+ With these closed, every phase of the
96+ ` 5spot-emergency-reclaim-by-process-match.md ` roadmap is shipped and
97+ the file moves to ` completed- ` prefix.
98+
99+ ### Impact
100+ - [ ] Breaking change (no API surface change; new metrics + new event
101+ reason are additive)
102+ - [x] Requires cluster rollout (binary changes; CRD unchanged so no
103+ ` kubectl apply ` of the CRD YAML needed)
104+ - [ ] Config change only
105+ - [ ] Documentation only
106+
107+ ### Verification
108+ - ` cargo build ` : clean.
109+ - ` cargo test ` : ** 450 unit tests + 3 always-on integration tests pass** ;
110+ 4 ` #[ignore] ` integration tests (2 emergency-reclaim, 2 netlink) gated
111+ on a real cluster / Linux + CAP_NET_ADMIN respectively.
112+ - ` cargo fmt --check ` : clean.
113+ - ` cargo clippy --all-targets -- -D warnings ` : clean.
114+
115+ ---
116+
117+ ## [ 2026-05-02 23:00] - Reclaim-agent rung 2: netlink proc connector subscriber
118+
119+ ** Author:** Erick Bourgeois
120+
121+ ### Added
122+ - ` src/netlink_proc.rs ` (~ 280 LOC) — new module exposing ` ProcEvent ` ,
123+ ` NetlinkError ` , portable parsers (` parse_cn_msg ` , ` parse_proc_event ` ),
124+ and ` Subscriber ` . Linux impl opens an ` AF_NETLINK ` /
125+ ` NETLINK_CONNECTOR ` socket, binds to the ` CN_IDX_PROC ` multicast
126+ group, sends the ` PROC_CN_MCAST_LISTEN ` control message, and yields
127+ one ` ProcEvent ` per kernel ` exec(2) ` . Non-Linux: ` Subscriber::new() `
128+ immediately returns ` NetlinkError::Unsupported ` so the binary still
129+ links cleanly on macOS / Windows builds.
130+ - ` src/netlink_proc_tests.rs ` — 14 byte-level parser tests + 1
131+ non-Linux stub contract test = ** 15 new tests, all running on macOS
132+ CI** without needing a kernel: exec / fork / exit classification,
133+ truncated buffers, wrong connector id, zero-length payload,
134+ cn_msg→proc_event round-trip, non-Linux ` Subscriber::new ` returns
135+ ` Unsupported ` .
136+ - ` src/bin/reclaim_agent.rs ` : new ` --detector={auto|netlink|poll} `
137+ flag (env ` RECLAIM_DETECTOR ` , default ` auto ` ). ` auto ` selects
138+ ` netlink ` on Linux and ` poll ` elsewhere. New ` run_netlink_scanner `
139+ loop runs alongside the existing ` run_scanner ` ; selection happens
140+ once after host-id verification, before either loop starts. The
141+ netlink loop opens the subscriber only when armed by a non-empty
142+ ConfigMap and tears it down on config-clear, so an idle agent holds
143+ no kernel resources. The ` recv(2) ` syscall runs on a ` spawn_blocking `
144+ thread; ` tokio::select! ` cancels via the existing config watch
145+ channel.
146+ - ` Cargo.toml ` : `target.'cfg(target_os = "linux")'.dependencies.nix =
147+ { version = "0.30", default-features = false, features = [ "socket",
148+ "net", "uio"] }`. Linux-only — non-Linux builds pull in nothing new.
149+ - ` deploy/node-agent/daemonset.yaml ` : container ` securityContext `
150+ gains ` capabilities.add: [NET_ADMIN] ` . The pod-level comment block
151+ rewritten to reflect that this is no longer "future work."
152+ - ` .trivyignore ` : new ` AVD-KSV-0022 ` entry justifying the
153+ ` CAP_NET_ADMIN ` add against the existing architectural-necessity
154+ rationale pattern (matches the KSV-0010 / KSV-0012 / KSV-0023 etc.
155+ blocks already in the file).
156+ - ` docs/src/concepts/emergency-reclaim.md ` : new "Detector — rung 1
157+ vs rung 2" subsection with the comparison table, selection flags,
158+ the heavy-exec-storm tradeoff, and a note on why the cap is granted
159+ pod-level rather than per-detector.
160+
161+ ### Changed
162+ - ` src/reclaim_agent.rs ` : ` match_pid ` visibility raised from ` fn ` to
163+ ` pub fn ` so the rung 2 netlink subscriber can resolve a kernel-pushed
164+ pid through the same match logic rung 1 uses. Body and behaviour
165+ unchanged. Existing 46 tests stay green.
166+ - ` src/lib.rs ` : ` pub mod netlink_proc ` added; module list comment
167+ block extended.
168+ - ` docs/src/concepts/emergency-reclaim.md ` status header updated:
169+ rung 2 is shipped, no longer "follow-up work."
170+
171+ ### Why
172+ Closes the deferred Phase 2 rung 2 of
173+ ` ~/dev/roadmaps/5spot-emergency-reclaim-by-process-match.md ` and
174+ GitHub issue #40 . Rung 1 already meets the <5 s SLA, so this is
175+ optimisation, not correctness — payoff is detection latency dropping
176+ from up to 250 ms (worst-case poll phase) to <10 ms (kernel push)
177+ and idle CPU dropping to zero on quiet nodes. The detector is opt-in
178+ via ` --detector=netlink ` ; ` auto ` (the default) picks netlink on Linux
179+ and poll elsewhere. Operators on heavy-exec workloads can pin
180+ ` --detector=poll ` to avoid the per-exec cost.
181+
182+ The netlink ABI is identical across Linux x86_64 and aarch64; both
183+ build targets in ` .github/workflows/build.yaml ` get rung 2 with no
184+ per-arch code. Windows is out of scope (no netlink ABI; would need an
185+ ETW-based sibling impl behind the same trait, separate effort).
186+
187+ ### Impact
188+ - [ ] Breaking change (new flag is opt-in via ` auto ` default; existing
189+ operators with ` RECLAIM_DETECTOR ` unset continue on the
190+ platform-appropriate default; rung 1 stays available as ` poll ` )
191+ - [x] Requires cluster rollout (DaemonSet manifest changes —
192+ ` kubectl apply -k deploy/node-agent/ ` to pick up ` CAP_NET_ADMIN `
193+ and the new env var support)
194+ - [ ] Config change only
195+ - [ ] Documentation only
196+
197+ ### Verification
198+ - ` cargo build --bin 5spot-reclaim-agent ` : clean.
199+ - ` cargo test --lib netlink_proc ` : 15/15 pass on macOS aarch64.
200+ - ` cargo test --lib reclaim_agent ` : 46/46 pass (unchanged from
201+ pre-change baseline).
202+ - ` cargo fmt --check ` + ` cargo clippy --all-targets -- -D warnings ` :
203+ clean.
204+ - Linux runtime verification deferred to CI (kind cluster) and to
205+ hardware testing — same gating as rung 1's integration test scope.
206+ The macOS dev loop uses ` --detector=poll ` and exercises the bin
207+ end-to-end without netlink.
208+
209+ ### Trust model (unchanged)
210+ The netlink socket is opened with ` CAP_NET_ADMIN ` granted at the
211+ container level. The node-scoped credentials posture is unchanged —
212+ the agent still binds only to the proc-event multicast group and
213+ cannot send control messages to other netlink subsystems. The
214+ host-identity cross-check (Phase 4 of the 2026-04-25 security audit)
215+ still gates every PATCH regardless of detector choice.
216+
217+ ---
218+
12219## [ 2026-05-02 10:00] - Symbol-import auto-VEX (roadmap Phase 3, scope-revised)
13220
14221** Author:** Erick Bourgeois
0 commit comments