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
23 changes: 23 additions & 0 deletions docs/adr/0001-episode-semantics-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,29 @@ algorithm and strict format-version binding. Rollback is removal of the v2
algorithm branch, schema definitions, and v2 golden; the v1 writer and artifacts
do not require migration.

### Mixed contrast means whole-selection rejection, not missing evidence

A focused three-candidate control resolves the first admission-atomicity
question without changing the v2 schema. The first and second threshold cores
have an `18x` bridge contrast, while the second and third have a `1x` contrast.
All three candidates remain selected; the adjacent decisions are therefore
`[pass, fail]` and the segment-level `admitted` value is false.

The aggregate means only that the complete v1 selection cannot be adopted
unchanged. It does not mean that every selected candidate or every boundary is
invalid. V2 must retain both local contrast records, including the passing
boundary, when the aggregate rejects the selection. Changing the aggregate from
`all` to `any` makes this control fail, so the test observes whole-selection
admission rather than merely counting contrasts.

This accepts the conservative all-adjacencies gate for the current research
oracle. It does not authorize dropping all findings in a runtime projection.
Partial adoption would require a separate boundary-driven partition contract:
how failed boundaries merge evidence, how an interior candidate with one pass
and one fail is assigned, and how resulting finding identities remain stable.
The first mixed control answers the aggregate-semantics question, so no cadence,
ratio, or candidate-count sweep is warranted in this slice.

Together, the two fixtures and focused tie/shared-evidence/background controls
accept the recovery, null-control, deterministic tie-break, and publication
single-consumption hypotheses for their bounded cases. The background control
Expand Down
126 changes: 113 additions & 13 deletions tests/test_episode_candidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
validate_oracle,
)
from scripts.episode_candidate_core import ( # noqa: E402
activity_segments,
enumerate_candidate_windows,
parse_timestamp,
select_window_separated_candidates,
Expand All @@ -38,6 +39,22 @@
)


def make_events(offsets: list[int]) -> list[dict[str, object]]:
origin = parse_timestamp("2026-03-10T09:00:00Z")
return [
{
"event_id": f"line:{index}",
"line_number": index,
"timestamp": (origin + timedelta(seconds=offset))
.isoformat()
.replace("+00:00", "Z"),
"event_type": "ssh_failed_password",
"source_ip": "203.0.113.77",
}
for index, offset in enumerate(offsets, start=1)
]


class ContinuousBackgroundFixtureTests(unittest.TestCase):
def setUp(self) -> None:
self.fixture = json.loads(
Expand Down Expand Up @@ -202,19 +219,7 @@ def test_v2_materializes_core_contrast_admission_without_changing_selection(
def test_v2_materializes_uniform_background_rejection_without_hiding_selection(
self,
) -> None:
origin = parse_timestamp("2026-03-10T09:00:00Z")
events = [
{
"event_id": f"line:{index}",
"line_number": index,
"timestamp": (origin + timedelta(seconds=150 * (index - 1)))
.isoformat()
.replace("+00:00", "Z"),
"event_type": "ssh_failed_password",
"source_ip": "203.0.113.77",
}
for index in range(1, 15)
]
events = make_events([150 * offset for offset in range(14)])
selected = select_window_separated_candidates(
enumerate_candidate_windows(events, 5, 600), 600
)
Expand All @@ -232,6 +237,101 @@ def test_v2_materializes_uniform_background_rejection_without_hiding_selection(
)
self.assertFalse(admission["adjacent_contrasts"][0]["passes"])

def test_v2_mixed_contrasts_reject_unchanged_selection_and_keep_boundaries(
self,
) -> None:
events = make_events(
[
0,
30,
60,
90,
120,
660,
1200,
1740,
2280,
2820,
3360,
3390,
3420,
3450,
3480,
*range(3630, 5581, 150),
]
)
selected = select_window_separated_candidates(
enumerate_candidate_windows(events, 5, 600), 600
)

admission = materialize_core_contrast_admission(events, selected, 5)

self.assertEqual(len(activity_segments(events, 600)), 1)
self.assertEqual(
[candidate.candidate_id for candidate in selected],
[
"candidate:line:1..line:5",
"candidate:line:11..line:18",
"candidate:line:23..line:27",
],
)
self.assertFalse(admission["admitted"])
self.assertEqual(
admission["reason_code"], "adjacent_core_contrast_below_minimum"
)
self.assertEqual(
[core["event_ids"] for core in admission["threshold_cores"]],
[
[f"line:{index}" for index in range(1, 6)],
[f"line:{index}" for index in range(11, 16)],
[f"line:{index}" for index in range(23, 28)],
],
)
self.assertEqual(
[
(
contrast["left_candidate_id"],
contrast["right_candidate_id"],
contrast["passes"],
contrast["reason_code"],
)
for contrast in admission["adjacent_contrasts"]
],
[
(
"candidate:line:1..line:5",
"candidate:line:11..line:18",
True,
"minimum_core_gap_contrast_met",
),
(
"candidate:line:11..line:18",
"candidate:line:23..line:27",
False,
"minimum_core_gap_contrast_below_minimum",
),
],
)
self.assertEqual(
[
(
contrast["bridge_mean_gap_microseconds"],
contrast["required_bridge_mean_gap_microseconds"],
)
for contrast in admission["adjacent_contrasts"]
],
[
(
{"numerator": 540_000_000, "denominator": 1},
{"numerator": 60_000_000, "denominator": 1},
),
(
{"numerator": 150_000_000, "denominator": 1},
{"numerator": 300_000_000, "denominator": 1},
),
],
)


class IsolatedDenseBurstsFixtureTests(unittest.TestCase):
def setUp(self) -> None:
Expand Down
Loading