Skip to content

fix(sessions): debounce and dedup watch() emissions Refs #815#860

Open
AlexMikhalev wants to merge 1 commit intomainfrom
task-815-debounce-session-watch
Open

fix(sessions): debounce and dedup watch() emissions Refs #815#860
AlexMikhalev wants to merge 1 commit intomainfrom
task-815-debounce-session-watch

Conversation

@AlexMikhalev
Copy link
Copy Markdown
Contributor

Summary

  • NativeTerraphim AIConnector::watch() previously emitted a duplicate session on every Modify inotify event, once per JSONL line-flush
  • Fixes with two-layer guard: debounce (200 ms quiescent window) + content dedup (suppress when messages.len() unchanged)

Changes

connector/native.rs

  • watch_at(base_path) private method for testable path injection, containing all debounce + dedup logic
  • watch() delegates to watch_at(default_path)
  • pending: HashMap<PathBuf, Instant> — per-path debounce clock; flush only after 200 ms silence
  • last_emitted: HashMap<PathBuf, usize> — tracks messages.len() at last emission; skips if unchanged
  • Blocking event loop in spawn_blocking; uses parse_session_file_sync + tx.blocking_send() to avoid nested-spawn stall
  • Exits cleanly when tx.is_closed()

connector/mod.rs

  • Updated watch() trait doc with formal Deduplication contract

Tests

  • test_parse_session_file_sync_dedup_key — unit test verifying dedup invariant without fileSystem watcher
  • test_watch_deduplicates_incremental_appends — end-to-end watch test marked #[ignore] (inotify/tmpfs timing-sensitive); run manually with -- --ignored
  • All 54 existing tests pass; 1 ignored; no clippy warnings

Verification

cargo test -p terraphim_sessions  →  ok. 54 passed; 0 failed; 1 ignored
cargo clippy -p terraphim_sessions -- -D warnings  →  Finished, no warnings

Prevent NativeTerraphim AIConnector::watch() from emitting a duplicate session
on every Modify inotify event.  The new watch_at() implementation:

- Collects fileSystem events into a per-path pending map; only flushes a
  path after a 200 ms quiescent window with no further events (debounce).
- Tracks messages.len() at last emission per path (last_emitted map) and
  skips emission when the count has not grown (content dedup).
- Runs the notify event loop synchronously inside spawn_blocking, using
  tx.blocking_send() to avoid nested tokio::spawn on single-threaded
  test runtimes.
- Adds parse_session_file_sync() as the blocking-thread counterpart to
  the async parse_session_file() method.

Unit test test_parse_session_file_sync_dedup_key() verifies the dedup key
invariant without requiring a live fileSystem watcher.  The end-to-end
watch integration test is marked #[ignore] with an explanatory comment
(timing-sensitive; run manually with -- --ignored).

Co-Authored-By: Terraphim AI <noreply@terraphim.ai>
@AlexMikhalev
Copy link
Copy Markdown
Contributor Author

Requirements Traceability Summary

PR #860 fixes a behavioural regression in NativeClaudeConnector::watch() — the method emitted a duplicate Session for every inotify Modify event (once per JSONL line-flush). The fix introduces a two-layer guard: a 200 ms debounce window plus content deduplication keyed on (path, messages.len).

Scope: single crate terraphim_sessions, two files changed.

Verdict: concerns

Implementation is clean and the unit test is solid. Two gaps reduce confidence: (1) no originating issue or ADR captures the watch() deduplication contract as a first-class requirement; and (2) the end-to-end integration test is permanently #[ignore], leaving the debounce timing path untested in CI.

Traceability Matrix

Req ID Requirement Design Ref Impl Ref Tests Status
REQ-001 watch() MUST NOT emit duplicate sessions for the same content within a quiescent window connector/mod.rs doc-contract (this PR) native.rs: watch_at(), pending: HashMap, last_emitted: HashMap test_parse_session_file_sync_dedup_key
REQ-002 Debounce window ≤ 250 ms per trait contract connector/mod.rs doc (≤ 250 ms stated) DEBOUNCE_MS = 200 No CI test covers timing path ⚠️
REQ-003 Dedup key: (path, messages.len) — emit only when message count grows connector/mod.rs doc-contract last_emitted map, new_len > prev_len guard test_parse_session_file_sync_dedup_key
REQ-004 Watcher exits cleanly when all receivers drop Not documented tx.is_closed() check in event loop Not tested ⚠️
INFERRED-001 watch_at() accepts injected path for testability Mentioned in PR description watch_at(base_path: PathBuf) split test_watch_deduplicates_incremental_appends (ignored) ⚠️
INFERRED-002 No originating issue/ticket for watch() dedup contract MISSING

Gaps

Blockers

  • INFERRED-002 — No issue / ADR traceable to the dedup contract. PR references #815 but that issue is feat(learn): compile corrections into live thesaurus — unrelated. The watch() deduplication requirement has no parent issue, ADR, or spec document. Traceability chain is broken at the requirement source.

Follow-ups

  • ⚠️ REQ-002 / INFERRED-001 — Integration test permanently ignored. test_watch_deduplicates_incremental_appends is marked #[ignore] with a comment that it is timing-sensitive on tmpfs. This means the debounce path (the primary fix) has no CI coverage. Recommend either: (a) a synthetic test that calls watch_at() on a real temp dir with a short DEBOUNCE_MS override, or (b) acceptance test gated on #[cfg(not(target_os = "linux"))] to skip tmpfs environments.

  • ⚠️ REQ-004 — Receiver-drop exit path untested. The tx.is_closed() loop exit is correct but has no test. A short unit test that drops the receiver and asserts the task terminates would close this gap.

  • ℹ️ The trait doc says "≤ 250 ms" but DEBOUNCE_MS = 200. These are consistent but the constant is not referenced in the doc — a minor coherence note.

Last spec-validated commit: 202042c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant