Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions docs/adr/0001-episode-semantics-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,20 @@ does not change the ordered oracle. This accepts the candidate as compatible
with already-correct isolated segmentation for this bounded null control; it
does not expand the candidate's production scope.

Together, the two fixtures accept the recovery and null-control hypotheses for
their bounded cases only. Maximal-window ties, shared evidence, background
calibration, and a production complexity design still require independent
evidence. `Detector::analyze()` and `loglens.report.v3` remain unchanged.
A focused maximal-window tie control uses six events at offsets 0, 1, 2, 3,
600, and 601 seconds. With threshold five and the inclusive 600-second window,
the only candidates are `line:1` through `line:5` and `line:2` through
`line:6`. Both cover five events, span 600 seconds, and require one episode, so
the chronological window key is the first objective that can distinguish them.
The earlier candidate wins for both forward and reversed candidate input. This
accepts the final tie-break and candidate-order invariance for that bounded
control; it does not settle the separate shared-evidence policy.

Together, the two fixtures and focused tie control accept the recovery,
null-control, and deterministic tie-break hypotheses for their bounded cases
only. Shared evidence, background calibration, and a production complexity
design still require independent evidence. `Detector::analyze()` and
`loglens.report.v3` remain unchanged.

## Alternatives considered

Expand Down
20 changes: 20 additions & 0 deletions tests/test_episode_candidate_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,26 @@ def test_overlapping_candidates_materialize_one_maximal_window(self) -> None:
self.assertEqual(len(selected), 1)
self.assertEqual(selected[0].event_ids, tuple(f"line:{i}" for i in range(1, 7)))

def test_equal_score_windows_prefer_chronological_key(self) -> None:
candidates = enumerate_candidate_windows(
make_events([0, 1, 2, 3, 600, 601]), 5, 600
)
expected = [tuple(f"line:{i}" for i in range(1, 6))]

self.assertEqual(
[candidate.event_ids for candidate in candidates],
[
tuple(f"line:{i}" for i in range(1, 6)),
tuple(f"line:{i}" for i in range(2, 7)),
],
)
for ordered_candidates in (candidates, list(reversed(candidates))):
selected = select_window_separated_candidates(ordered_candidates, 600)

self.assertEqual(
[candidate.event_ids for candidate in selected], expected
)

def test_research_size_limits_fail_closed(self) -> None:
with self.assertRaisesRegex(ValueError, "at most 200 events"):
enumerate_candidate_windows(make_events(list(range(201))), 5, 600)
Expand Down
Loading