Skip to content

fix(merge): classify stale preparations by a stable error code - #338

Merged
beinan merged 1 commit into
lance-format:mainfrom
beinan:fix/typed-maintenance-error-codes
Oct 8, 2026
Merged

beinan merged 1 commit into
lance-format:mainfrom
beinan:fix/typed-maintenance-error-codes

Conversation

@beinan

@beinan beinan commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Part of #333 (P0.2, backlog C1).

Problem

merge/failure.rs recognised stale index/compaction preparations by matching exact prose plus the literal path crates/lance-context-core/src/store_base.rs: in the string Lance renders for InvalidInput. Moving the check, renaming the crate, or a change in Lance's wrapper format silently reverts a 2 s retry to a 1 h cooldown with needs_attention — the production failure #328 fixed. (Line numbers are not the fragility; any digits are accepted.)

Approach

  • lance-context-api: MaintenanceErrorCode::StalePreparation → [LC_STALE_PREPARATION], with tag() / parse(). The api crate hosts it so core, merge and master share it without a dependency cycle.
  • store_base.rs: all six stale-preparation sites tag their message (index ×3, compaction ×2, merge watermark overlap ×1).
  • failure.rs: classify checks ownership first, then the code, then falls back to the legacy text matcher for records written by old binaries. corrected() accepts either form. The legacy matcher is marked removable once no stored record lacks a code.
  • docs/concurrent-indexing.md updated.

Alternatives

A typed error enum through lance::Error is cleaner, but errors cross worker HTTP and etcd task records as strings; a code inside the string is the smallest change that survives every boundary. Leaving the text matcher as the only path: rejected, it is the bug's root cause.

Validation

  • New tests: code round-trip through wrappers and arbitrary paths; ownership precedence; code does not launder a 403; old DataOrConfiguration record with a code is corrected without resetting attempts/clock.
  • lance-context-merge: 11 unit + 10 etcd-backed tests pass locally; lance-context-core prepared/stale tests pass; clippy --all-targets -D warnings, fmt, typos clean.

🤖 Generated with Claude Code

The failure classifier recognised stale index/compaction preparations by
matching the exact prose and the literal source path
`crates/lance-context-core/src/store_base.rs:` inside the string Lance
produces for `InvalidInput`. Moving the check to another file, renaming
the crate, or a change in how Lance renders the wrapper would silently
turn a two-second retry back into a one-hour cooldown with
`needs_attention` set - the production failure lance-format#328 fixed.

Add `MaintenanceErrorCode` to lance-context-api with one variant,
`StalePreparation`, rendered as `[LC_STALE_PREPARATION]`. Every
stale-preparation error site in store_base.rs (index ×3, compaction ×2,
merge watermark overlap ×1) tags its message. `classify` checks
ownership first, then the code, then falls back to the legacy text
matcher for failure records written by older binaries; `corrected()`
accepts either form. The legacy matcher is now documented as removable
once no stored record lacks a code.

Alternatives: a typed error enum through lance::Error would be cleaner
but errors cross HTTP and etcd as strings; a code inside the string is
the smallest change that survives every boundary. The api crate hosts
the enum so core, merge and master share it without a cycle.

Validation: new unit tests cover code round-trip through wrappers and
paths, ownership precedence, and correction of old records carrying a
code. merge crate: 11 unit + 10 etcd tests pass; core prepared/stale
tests pass; clippy -D warnings, fmt and typos clean.

Refs lance-format#333.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@beinan
beinan merged commit 04e5e9a into lance-format:main Oct 8, 2026
16 checks passed
beinan added a commit that referenced this pull request Oct 8, 2026
…/403 (#347)

Part of #333. Fixes two defects in #338 found in review (credit:
production review).

## Problems

1. **Mixed-version regression (P1).** #338 changed the exact string old
masters match on. A stale preparation produced by a new executor and
classified by an old master → `DataOrConfiguration`, one-hour cooldown —
the bug #338 set out to fix. Reproduced with old vs new classifier.
2. **Code outranks a 403 (P2).** `classify` checked the code before HTTP
status, so `HTTP 403: … [LC_STALE_PREPARATION]` → `Retryable`. The #338
description claimed a test for this; there was none. `corrected()` also
had its own rule instead of sharing `classify`'s.

## Fixes

- Emission **off by default**; `MAINTENANCE_ERROR_CODES=1` enables it
after every master/executor runs a code-aware reader. `tag()` returns
the exact legacy string when off; `tag_forced()` for tests.
- Code moved to a **suffix**; the legacy text matcher tolerates the
suffix (defence in depth for a reader with only the text path).
- `classify` order: ownership → explicit 401/403 → code → text.
`corrected()` delegates to `classify` and only softens a stored
`DataOrConfiguration`.
- `docs/concurrent-indexing.md` documents the rollout order.

## Validation

- New: 401/403 wrapping the code in either position →
`DataOrConfiguration`, and `corrected()` leaves it; emission off by
default; byte-exact legacy string still matched; tagged string
recognised by text path alone.
- `lance-context-api` 1/1, `lance-context-merge` failure 7/7,
`lance-context-core` prepared/stale 9/9; clippy `-D warnings`, fmt,
typos clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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