catalog: keep the load path fail-closed and clear the three clippy errors

The hosted Lint and Safety jobs both pinned this tree at `d05d883bc4` and both
failed inside `codewhale-tui`, so one slice closes them together.

Clippy, three errors under `-D warnings`, taken verbatim from the job log:
- `client/provider_native_search.rs:700` called `filter(..).next_back()` on a
  `DoubleEndedIterator` -> `rfind(..)`, same last non-empty segment in one pass.
- `models_dev_live.rs:350` and `:435` redundant closure -> pass
  `note_persistence_error` directly.

Safety gate: `provider_catalog_live::tests::
durable_provider_catalog_cannot_claim_cloud_or_override_price_authority`
panicked at `:3243` with `forged scope must be dropped`. `f173c4829f` made the
loader keep a scope and drain unusable rows one at a time, which also kept a
scope whose *every* row failed validation — exactly the forged-scope shape the
test exists to refuse. A scope that arrives with rows and loses all of them is
dropped again, gated on `rows_before > 0` so a genuine empty-on-write scope
still loads.

CHANGELOG gains the bullet `f173c4829f` earned and never got: the OrcaRouter
refresh that reported `failed / cache_write_failed / model_count 0` against a
468-row endpoint lands 449 rows now, and a failed refresh names its typed
reason instead of flattening it.

Checks on this tree:
- `cargo clippy --workspace --all-targets --all-features --locked -- -D
  warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A
  clippy::unnecessary_map_or` (the CI invocation verbatim) -> exit 0, zero
  `error:` lines, 9m05s.
- `nextest run -p codewhale-tui --lib -E 'test(provider_catalog_live::tests) |
  test(models_dev_live::tests)'` -> 35 run, 35 passed, 15352 skipped,
  including the previously failing forged-scope case and the OrcaRouter
  neighbour `disk_reload_rehydrates_and_exposes_six_hundred_openrouter_models`.

Not verified: hosted CI, which runs once at the release gate.
This commit is contained in:
CodeWhale Bot committed 2026-10-23 12:50:11 -07:00
1 parent d7674aa3b2
commit 6467fc28ee
4 files changed
+22 -3

No files matched your search

+11
View File
@@ -117,6 +117,17 @@ and independent workflow steps can continue after another child fails.
snapshot, and an unfresh one still contributes its own ids as an offline
tail so a withdrawn model does not vanish by omission.
- One catalog row without a price no longer costs you every model in its
provider. `models --update` reported `failed / cache_write_failed` with
`model_count 0` for OrcaRouter while the endpoint answered 468 rows, because
a row whose price was dropped still carried a live cost stamp and one such
row rejected the whole refresh as `invalid_response`; the same all-or-nothing
rule also discarded an entire cached scope on the next read. Rows are now
judged one at a time, a refresh fails only when rows arrived and none
survived, and a cached scope that loses every row to validation is still
dropped rather than loaded. A failed refresh names its typed reason instead
of `cache_write_failed` and still reports the rows you have left.
- A new OrcaRouter session with no saved wire model defaults to
`deepseek/deepseek-v4.1-flash-1017`. Every OrcaRouter catalog row has
`default_for_provider` false, so the descriptor was the only thing deciding
@@ -697,7 +697,7 @@ fn fallback_title(url: &str) -> String {
};
let slug = parsed
.path_segments()
.and_then(|segments| segments.filter(|segment| !segment.is_empty()).next_back());
.and_then(|mut segments| segments.rfind(|segment| !segment.is_empty()));
let Some(slug) = slug else {
return format!("{host} (home page)");
};
+2 -2
View File
@@ -347,7 +347,7 @@ pub async fn refresh(force_network: bool) -> Result<usize, ModelsDevRefreshError
body,
},
)
.inspect_err(|error| note_persistence_error(error))?;
.inspect_err(note_persistence_error)?;
}
Ok(count)
}
@@ -432,7 +432,7 @@ async fn refresh_from_path(path: &Path) -> Result<usize, ModelsDevRefreshError>
body,
},
)
.inspect_err(|error| note_persistence_error(error))?;
.inspect_err(note_persistence_error)?;
}
Ok(count)
}
+8
View File
@@ -869,6 +869,14 @@ fn load_from_disk_unlocked_with_limit(path: &Path, max_bytes: u64) -> Option<Pro
"dropped unusable rows from a cached provider catalog"
);
}
if rows_before > 0 && entry.offerings.is_empty() {
tracing::debug!(
target: "provider_catalog",
scope = %entry.provider,
"dropping provider catalog scope whose every row was unusable"
);
return false;
}
true
});
if cache.entries.len() != scopes_before {