A zero-mandatory-dependency temporal leakage guard for Python and Jupyter. Make the historical information boundary executable in CI.
Separate event time from known_at and eligible_from, then use the optional pandas helper to join only vintages that were actually available.
Scan Python and Jupyter for negative shifts/diffs, backward fills/interpolation, centered windows, forward/nearest as-of joins, same-dataframe whole-series feature aggregates, and negative absolute iloc.
Mutate or delete only future rows and verify that already-produced historical outputs stay invariant.
# tomorrow's price in today's feature
lead = price.shift(-1)
# future rows in a centered window
smooth = price.rolling(7, center=True).mean()
$ nofuture scan src notebooks
FAIL: 2 finding(s)
[SRC001] negative shift can read future rows
[SRC003] centered rolling windows can include future rows
Eighteen planted leak cases and thirteen safe controls exercise every shipped semantic static rule (SRC001+), including the opt-in temporal-context rules and targeted suppression. The benchmark reports exact-case accuracy, finding precision/recall/F1, leak detection, specificity, and per-rule coverage. The corpus and runner live in benchmarks/ and run in CI on every push.
Each semantic source rule has one tiny leaking notebook and one paired safe notebook. CI verifies exact rule IDs, notebook cell numbers, canonical fixture contents, and source-only cleanliness for the context-gated rules.
Static AST rules catch explicit known patterns. Prefix and future-mutation invariance also catch indirect future dependencies. The experiment keeps two safe controls clean and makes the boundary between the methods measurable.
A fixed-seed forecasting experiment keeps the OLS model and chronological split unchanged, then adds one unavailable future feature. R² jumps by 0.361 and RMSE falls to 0.158× the causal baseline. The experiment is synthetic and explicitly not a real-world effect-size estimate.
The external corpus now spans nine projects/backends: Freqtrade, pandas, scikit-learn, Polars, Xarray, NumPy, Dask, PySpark pandas, and Snowpark pandas. Existing negative-shift/diff semantics transfer across those dataframe implementations without backend-specific detector code; explicit context recovers single-level, nested, grouped/panel, and metadata-routing-capable scikit-learn CV helpers while paired TimeSeriesSplit controls stay clean. The NumPy negative-roll case remains a narrow context-only rule with a paired lag control.
A separate GitHub-issue corpus turns real user reports into regression evidence without pretending every report is maintainer-validated. Layered static + availability checks surface both reported leaks, including one source-only miss. A Freqtrade maintainer-confirmed callback case remains deliberately visible as a static SRC009 false-positive boundary.
Future-mutation tests reject counterfactuals that alter history, change row count, or violate a caller-supplied input contract. Bounded-probability, OHLC, normalized-vector, and monotonic cumulative experiments show domain-valid mutations keeping causal transforms clean while exposing planted future dependencies.
Across four domains, three historical cut points, and two valid mutation strengths, every causal control stays clean and every planted future dependency remains detectable. No best-case intervention is selected after the fact.
Prefix and future-mutation invariance now cover ordinary, grouped/stateful, irregular missing-data, as-of alignment, resampling-boundary, and multi-column pandas transforms: nine causal controls stay clean and nine future-dependent transforms are detected under explicit future-only mutation contracts.
A fixed-seed generator creates 24 append-only vintage histories with explicit eligibility delays. Valid histories stay valid after row shuffling, early decisions cannot see not-yet-eligible vintages, post-eligibility decisions select the same latest vintage after reordering, and duplicate known_at values consistently produce REV001.
Hypothesis searches 64 generated revision histories across 12 invariants, including multi-column revision keys, null reasons, missing-key rejection, and null-to-value revisions. If one fails, the strategy shrinks the history toward a smaller counterexample while the fixed-seed suite remains available for exact reproduction.
Read the research brief → · Full evaluation, falsification criteria, provenance, and reproduction commands →
$ nofuture audit-manifest temporal-contract.json
PASS: 0 finding(s), 3 row/line(s) scanned
Commit availability columns and revision keys as a small JSON contract, then fail CI when a dataset becomes temporally ambiguous. Manifest reference.
- uses: actions/checkout@v7
- uses: ORANGINGS/nofuturedata@v0.4.0
with:
path: src
Also available as a pre-commit hook, a Python API, a CLI, and SARIF output for GitHub Code Scanning.