Leakage review: Phase 3 time slicer and constraints¶
Reviewer: leakage-reviewer subagent (read-only; it did not write the code), 11 Oct 2026.
Scope: src/driftwatch/data/ constraints, slicer, tables, presets, canary, synthetic, loaders (public view), scripts/leakage_canary.py, and their tests. Code at commit 4ce5cba plus ADR-0005 fc36e5b.
Method: the reviewer read the files in full and ran the existing tests (115 passed). It could run only uv run pytest, so it did not execute probes of its own.
Result of pass 1: 14 findings: 0 blockers, 3 major, 11 minor. No C1, C2 or C3 violation was found on the table or cached paths. The risk it identified is at the interface to later phases.
What it checked and found sound¶
C1.
check_c1is strict, raises on equal timestamps, empty sides and missing timestamps, and runs per evaluation slot against the real training timestamps. Slot assignment is half-open everywhere.C2, cached path. Every row’s timestamp lies inside its base unit and so inside its slot, by construction of the ISO-week and month units and the Monday-anchored grid.
C3. Malware-only, seeded by
(seed, crc32(slot id)), independent of window and of input row order, exactFractionarithmetic, nested across ratios, counts in the manifest.Gate and gaps. Empty, partial and failing slots are never in a window;
_open_windowre-asserts a contiguous, usable grid; skips are logged with reasons.Canary. The shuffled and random-split baselines are leak-free; the training size comes from the plan, not from what a leaky slicer returned.
Findings and dispositions¶
# |
Severity |
Finding |
Disposition |
|---|---|---|---|
1 |
major |
The “opaque” |
Fixed ( |
2 |
major |
|
Fixed. |
3 |
major, carried |
A |
Carried to Phase 5. No selector, policy or monitor exists yet. Rule for Phase 5: they receive only |
4 |
minor |
C2 ran only after subsampling, so a stray out-of-window malware row could be subsampled away unseen; training rows had no window check |
Fixed. |
5 |
minor |
|
Fixed. Renamed |
6 |
minor |
|
Fixed. Statistical test on the draw’s position distribution and mutant |
7 |
minor |
The C2 and C3 call sites were pinned only through the oracle |
Fixed. |
8 |
minor |
The production-like configuration (anchor, exclusion) was exercised only by the full-data run |
Fixed. |
9 |
minor |
|
Fixed. |
10 |
minor |
A |
Fixed. A filter sees only its declared columns; the cache planner no longer passes labels. The BODMAS placeholder rule is class-conditional by effect (it removes benign rows only), which ADR-0004 accepts and the manifest counts per class |
11 |
minor |
The public view shared a mutable feature array with the scoring path |
Fixed by the public copy in 1. |
12 |
minor |
The BODMAS rows published without a UTC offset (read as UTC) were not reported per slot |
Fixed. |
13 |
minor |
A base unit emptied by the filter did not make its slot partial |
Fixed. The slot is marked partial and is therefore unusable ( |
14 |
minor |
ADR-0005 cited this file before it existed |
Fixed by this file |
Pass 2: re-review of the fixes (commit ee4bcd0)¶
Reviewer: leakage-reviewer subagent again, read-only; 142 tests passed on its run. Verdict: no C1, C2 or C3 violation and no sealed-label problem in the slicer remains unresolved. All six claimed fixes held and introduced no new leakage path (the public permutation is drawn from the row count only, with entropy independent of the C3 draw). It reported 9 further findings (1 major latent, 7 minor, 1 informational):
# |
Severity |
Finding |
Disposition ( |
|---|---|---|---|
P1 |
major, latent |
|
Fixed. The selector-facing view is a distinct |
P2 |
minor |
The opacity test checked linear correlation only; a class-aware regular order would pass |
Fixed. Metamorphic test: identical counts, different label placement, identical permutation; mutant |
P3 |
minor |
Handles are slot-local; resolving against the wrong slot would silently release other labels |
Fixed. |
P4 |
minor |
The public order is a pure function of the seed and slot id, so a consumer that deliberately recomputes it could invert it |
Accepted, documented. This is accident-proofing, not an adversarial boundary: consumers never import slicer internals, and the Phase 5 spy test enforces the type boundary. A per-run secret would add state for no protection against a deliberate insider |
P5 |
minor |
|
Fixed. A digest of the source provenance (row filter, length, anchor, exclusions, manifest hash) is part of the id |
P6 |
minor |
|
Fixed. Only a |
P7 |
minor |
Iterating a window twice duplicated manifest rows |
Fixed. Rows are keyed by (window id, role, slot id) and replaced |
P8 |
minor |
A slicer violation inside the canary exited 1 (same as FAIL); PASS with an AUT above 0.9 exited 0 |
Fixed. Violation exits 4; PASS with the warning exits 3 |
P9 |
informational |
|
Carried to Phase 5, which must build the mask from the simulator’s real training pool and give it to the metrics only, not to the public view |
Evidence¶
Fast suite after the pass-2 fixes: 481 passed. Kill matrix: 15 of 15 mutants killed, baseline 120 tests green (
reports/mutation_matrix.md, commit9b27b58).Real caches after the pass-2 fixes: LAMDA 47 windows and 564 evaluation slots, BODMAS 2 windows and 24 evaluation slots, no C1-C3 violation, nothing densified.
Evidence (pass 1)¶
Fast suite after the pass-1 fixes: 472 passed. Fixes were written test-first; the 12 new regression tests failed on the code as reviewed.
Kill matrix: 14 of 14 mutants killed, baseline green (
reports/mutation_matrix.md). The two new mutants areoldest_malwareandpublic_ids_are_row_ids.Real caches after the fixes: LAMDA 47 windows and 564 evaluation slots, BODMAS 2 windows and 24 evaluation slots, no C1-C3 violation, nothing densified, peak 0.36 and 0.65 GB. Window options unchanged.
Not covered (code does not exist yet)¶
The sim/queue.py delay rule, fit scope for scalers and thresholds, and conformal calibration contamination. Hard rules 4 (spy test), 6 and 7 remain to be verified when those phases land; finding 3 above is their precondition.
Deviations recorded¶
The canary movement is tested on seeded synthetic timelines with planted drift, because the committed fixture has features independent of labels and cannot decay. The fixture caches prove the script runs end to end.
C3 follows the researcher’s “cap and flag” choice (ADR-0005): a slot already below the target ratio keeps all rows and is flagged
below_target.