Skip to content

feat(item108): Ibis join_asof strategy semantics (forward/nearest support) - #305

Merged
discreteds merged 6 commits into
developfrom
feature/item-108-ibis-asof-strategy
Aug 19, 2026
Merged

feat(item108): Ibis join_asof strategy semantics (forward/nearest support)#305
discreteds merged 6 commits into
developfrom
feature/item-108-ibis-asof-strategy

Conversation

@discreteds

Copy link
Copy Markdown
Member

Summary

Implements spec 2026-08-18-ibis-asof-strategy-design.md (draft 2) / plan 2026-08-18-ibis-asof-strategy.md (draft 5, narrowed-scope architecture after 4 rounds of adversarial GLM-5.3 review).

Closes the silent wrong-data path on Ibis (Relation.join_asof(strategy="forward"|"nearest") previously silently returned backward's results on every Ibis dialect). Fixes the narwhals-pandas nearest genuine-cross-side-tie defect and a colliding-payload-name crash. Fixes a schema leak ({on}_right) and a nondeterminism bug (duplicate-left-row order varying run-to-run) in the native Ibis backward path. Retires IB-REL-13 (ibis-sqlite gains join_asof support for every strategy via an Ibis-relational-expression emulation).

Three narrow, non-value-affecting divergences (duplicate-tie winner and interleaved-by-group row order on Ibis SQL dialects and narwhals-pandas) are declared via DivergenceFact/xfail_divergence — the codebase's existing mechanism for dozens of comparable backend quirks — rather than chased with increasingly intricate emulation code. Every declared divergence is backed by a probe-verified DivergenceFact and an xfail_divergence-marked test that would catch a silent behavior change.

Architecture

  • backward uses native asof_join on ibis-duckdb/ibis-polars (closest to the original design).
  • forward/nearest (any SQL dialect) and every strategy on ibis-sqlite route through an Ibis-relational-expression emulation (inner candidate join → windowed rank → left-keep join).
  • strategy joined gate_params; a new predicate CapabilityFact permanently gates forward/nearest on ibis-polars (non-equality join predicates are rejected there).
  • narwhals-pandas/pandas nearest uses a dual backward/forward join + distance comparison to fix the genuine cross-side tie (Polars' documented forward-wins rule).

New divergences (declared, not fixed)

  • IB-REL-15ibis-polars permanently rejects forward/nearest (non-equality predicates unsupported).
  • IB-REL-16ibis-duckdb native backward picks the FIRST duplicate-right-key row on a tie; Polars picks the LAST.
  • IB-REL-17 — Ibis SQL-backend paths group output by by value; Polars preserves left input order for interleaved groups.
  • IB-REL-14ibis-sqlite has no TimestampDelta translation; temporal nearest/tolerance unsupported there.
  • NW-REL-03/NW-REL-04/NW-REL-05 — narwhals tolerance gate, null-key raise, and nearest duplicate-tie-winner divergence on the pandas-family dialects.
  • IB-REL-13 retired (closed in registry/upstream-issues.yaml, not deleted, per the IB-REL-11 precedent).

Verification

  • Version-stamped probe (scripts/probes/probe_asof_emulation.py): polars 1.43.2 | ibis 12.0.0 — ALL PASS, including a 10-rep determinism check on the native duckdb path.
  • Full targeted suite: tests/relations/cross_backend/test_rel_join_results.py, test_rel_extension_ops_results.py, test_rel_operation_keys.py, capability registry/predicate-probe/census/integrity/enforcement, divergence facts, upstream registry join/validate/audit — all green.
  • Broad regression sweep: tests/relations/ + tests/core/ — 8141 passed, 0 failed, 68 skipped (pre-existing), 720 xfailed (expected divergences).
  • ruff check clean. mypy diffed byte-identical against pristine develop (98 pre-existing import-untyped/duplicate-module errors, zero new — confirmed unrelated to this change).

Review history

GLM-5.3 design review (job 016): Sound with required changes. Plan reviews (jobs 017–019): 3 rounds, each finding new bugs concentrated in one place — code chasing exact Polars parity on things Polars itself treats as an implementation convention (duplicate-tie winner, interleaved-group row order), never in the actual value-matching logic (correct in every round). User directed a scope narrowing: fix the real defects, declare the narrow edge cases. Draft 5 reflects that decision and was independently re-verified against the installed libraries (not just review prose) before implementation began.

…ward)+emulation, retire IB-REL-13

Probe evidence (polars 1.43.2 | ibis 12.0.0): ALL PASS (scripts/probes/probe_asof_emulation.py)
…5 entry, regenerate all generated artifacts

Added assert_predicate_capability_gated() to tests/fixtures/capability_gating.py:
capability_gate()/assert_capability_gated() only resolve static family/dialect/param
facts, never predicate facts (which need actual bound call values to evaluate) -
the new helper is the predicate-fact counterpart, keeping the raw pytest.raises
form out of migrated test files per test_no_migrated_site_carries_a_raw_capability_form.

hatch run mypy:check confirmed pre-existing broken on develop (identical 98 errors,
same duplicate-module-name root cause) - zero new errors from this change (diffed).
test_json_completeness caught a real gap: _fact_dict (render_markdown.py) never
serialized CapabilityFact.predicate at all, so test_json_completeness's own
JSON-vs-live-model fact-multiset comparison hardcoded () for every fact's
predicate term (previously harmless - no production predicate fact existed to
expose it). Item 108's IB-REL-15 predicate fact is the first one, and tripped
the drift the hardcoded () was silently hiding.

Added a kind-tagged JSON encoding (_operand_json/_clause_dict) mirroring
schema._operand_key's own tagging, plus the test-side inverse
(_operand_key_from_json/_clause_key_from_json) so the identity round-trips
exactly through the wire format. Full tests/relations + tests/core sweep
(8141 passed) confirms no other predicate-fact-shaped gaps remain.
@discreteds
discreteds merged commit 7596340 into develop Aug 19, 2026
4 checks passed
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