Allowlisting a model does nothing until a poll ingests it, and the poller timer runs every 2 hours. Observed live today: two models were allowlisted at 18:59, the poller had last run at 17:46, and their catalog rows still read `deprecated` with a `last_updated` three days old. That reads as "the allowlist did not work", not "the catalog has not caught up yet" -- and there is nothing in the portal that says which. Removal matters as much as addition, and is the less obvious half: the poller is what marks a dropped row `deprecated`, so without this a de-allowlisted model keeps serving traffic until the next tick. **Debounced, not fire-per-edit.** The allowlist editor adds entries one at a time, so a five-model session would otherwise mean five full multi-provider catalog fetches. Each edit cancels the pending timer and starts a new one, so a burst costs exactly one poll -- `test_a_burst_of_edits_costs_exactly_one_poll` pins that. Three things it deliberately does not do: - It does not run inside the endpoint's transaction. `poller.main` opens its OWN connection, so scheduling before the commit would race it against an uncommitted write -- and the poll would then filter the catalog against an allowlist missing the row that triggered it, deprecating the very model just added. - It does not fail the edit. The edit is already committed; surfacing a network error from the re-poll would report the wrong thing as broken, and the timer retries on its own schedule regardless. - It does not live at module level. State is in `build_router`'s closure, keeping the module's no-globals contract. `freshness.repoll_after_allowlist_change_seconds` (5.0) tunes it; 0 disables the trigger and leaves the timer as the only path. tests/conftest.py disables the trigger suite-wide -- `poller.main` reads its own config, opens its own DB and talks to the network, so the existing allowlist tests would have started firing real polls the moment this shipped. The new tests clear that guard and substitute the runner, so the debounce is covered without the side effects. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRQXz5SYZYVWscxS1QqF6U
40 lines
1.8 KiB
Python
40 lines
1.8 KiB
Python
"""Suite-wide isolation from the machine's own configuration.
|
|
|
|
`load_config` merges `config/config.local.yaml` next to `config.yaml`, and
|
|
`dispatcher.cfg` is bound at import time, so without this the whole suite
|
|
reads whichever overlay this deployment happens to have.
|
|
|
|
That was not hypothetical. With `classifier.mode: local_encoder` in the
|
|
overlay -- the setting this deployment actually runs -- nine tests failed
|
|
with assertions like `assert 'local_encoder' == 'local_llm'`: five in
|
|
test_classifier_backoff, three in test_gaming_mode, one in
|
|
test_classifier_modes_dispatch. Reproduced both directions on the same
|
|
tree: overlay absent, 1523 passed; overlay present, 9 failed. A deployment
|
|
that had configured anything could not run its own tests clean, and the
|
|
failures looked like real regressions.
|
|
|
|
This must run before anything imports `dispatcher`, which is why it is a
|
|
module-level statement in conftest rather than a fixture: pytest imports
|
|
conftest first, and `dispatcher.cfg` is built during the first
|
|
`import dispatcher` in any test module.
|
|
|
|
A test that genuinely wants overlay-merging behaviour should write its own
|
|
overlay in a tmp_path and point `base_dir` at it -- see
|
|
tests/test_admin_provider_readonly.py -- rather than depending on the
|
|
developer's machine.
|
|
"""
|
|
|
|
import os
|
|
|
|
os.environ["ROUTER_IGNORE_LOCAL_CONFIG"] = "1"
|
|
|
|
# An admin allowlist edit schedules a real catalog poll a few seconds later:
|
|
# poller.main() reads its OWN config, opens its own DB and talks to the
|
|
# network. None of that belongs in a unit test, and the existing allowlist
|
|
# tests would have started firing it the moment the trigger shipped.
|
|
#
|
|
# The scheduling logic itself is still covered -- tests/test_admin_allowlist.py
|
|
# clears this and substitutes a fake runner, so the debounce is tested without
|
|
# the side effects.
|
|
os.environ["ROUTER_DISABLE_BACKGROUND_POLL"] = "1"
|