mirror of
https://github.com/KohakuBlueleaf/KohakuHub.git
synced 2026-07-10 16:20:59 -05:00
Locks down the strict-freshness cache contract designed in #79: - Cache key now `(user_id|anon, tokens_hash, repo_type, namespace, name)`; new `compute_tokens_hash` derives a stable 16-hex prefix from the user's effective per-source tokens (DB + Authorization-header passed). Two users sharing a repo, or one user swapping external tokens between requests, get isolated cache buckets. - Three monotonic generation counters (`global_gen`, `user_gens[uid]`, `repo_gens[(rt, ns, name)]`) guard against the "an invalidation event lands mid-probe and the probe writes a now-stale binding" race. `safe_set` reads a snapshot at probe entry and rejects the write if any of the three has been bumped — closing all three race classes (admin source mutation, user token rotation, repo CRUD). - New cache primitives: `invalidate_repo(repo_type, ns, name)` (clears every user bucket for one repo, bumps `repo_gens[...]`) and `clear_user(user_id)` (clears every repo bucket for one user, bumps `user_gens[uid]`). - Invalidation hooks wired into: - `api/auth/external_tokens.py` POST/DELETE/PUT bulk → `clear_user` - `api/repo/routers/crud.py` /repos/create, /repos/delete, /repos/move (both old and new ids) → `invalidate_repo` - `api/settings.py` PUT settings (only when `private` flips) → `invalidate_repo` - `api/admin/routers/fallback.py` POST /fallback-sources → `cache.clear()` (parallels existing UPDATE/DELETE) - New admin endpoints for operational hygiene: - DELETE /admin/api/fallback-sources/cache/repo/{rt}/{ns}/{name} - DELETE /admin/api/fallback-sources/cache/user/{user_id} - `try_fallback_*` operations and `_run_cached_then_chain` thread the authenticated `user` (or `None`) and `tokens_hash` through the cache layer; decorator wires `user=user` into all four op variants. Test coverage: - `cache.py` 100% (35 tests in `test_cache.py`: hash invariance, per-user/per-tokens isolation, eviction scope, gens, safe_set acceptance/rejection on each dimension, key-shape pinning) - `operations.py` 100% (existing test suite + new `test_strict_freshness.py` 8 tests for per-user binding isolation and three race classes via the live op layer) - API hooks: `test_cache_invalidation_hooks.py` 15 tests, one per documented event in the invalidation matrix - HF-Hub end-to-end: `test_strict_consistency.py` adds two new tests using the real `huggingface_hub` library + scenario_hf_server to prove per-user bucket isolation and external-token-rotation eviction across the wire. 347 tests pass across the changed-file scope; cache.py + operations.py both at 100% line coverage. Closes #79 Refs: #77, #78 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.3 KiB
4.3 KiB
Changelog
All notable changes to KohakuHub are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
- Fallback cache: strict-freshness contract (#79).
Cache key now includes
user_idand atokens_hashderived from the user's effective external tokens. Two users (or one user with different per-requestAuthorization: Bearer ...|url,token|...external tokens) can no longer share a binding. Three monotonic generation counters (global_gen,user_gens[uid],repo_gens[(rt, ns, name)]) drive asafe_setrace-protection check that rejects cache writes whose pre-probe snapshot disagrees with the post-probe state — closing the admin-mutation / token-rotation / repo-CRUD races where a probe in flight could otherwise pin a stale binding after an invalidation. - Fallback cache: invalidation hooks (#79).
cache.invalidate_repo(repo_type, ns, name)andcache.clear_user(user_id)provide the two new eviction primitives; hooked into local repo create/delete/move/visibility-toggle, user external-token POST/DELETE/PUT bulk, and admin source create (the last one parallels the existing UPDATE/DELETE that already evicted). - Admin endpoints: per-repo and per-user fallback cache eviction
(#79). New
DELETE /admin/api/fallback-sources/cache/repo/{repo_type}/{namespace}/{name}andDELETE /admin/api/fallback-sources/cache/user/{user_id}for operational hygiene. - Fallback: repo-grain binding. Within a single bind window every read
against one
repo_idgoes to exactly one source. Eliminates cross-source mixing where the SPA showed source A's metadata whileresolveserved source B's bytes for the same repo. Implements the contract from #75. (#77) - Fallback: per-loop, per-repo binding lock. Concurrent cache-miss callers serialize on the lock so all observers agree on the same bound source instead of independently scanning the chain. (#77)
- Fallback: three-state classifier
FallbackDecision.BIND_AND_RESPOND/BIND_AND_PROPAGATE/TRY_NEXT_SOURCEinkohakuhub.api.fallback.utils.classify_upstream, mirroringhuggingface_hub.utils.hf_raise_for_statuspriority order (X-Error-Codewins over numeric status). (#77)
Changed
- Fallback:
disabledupstream marker now classifies asTRY_NEXT_SOURCEinstead ofBIND_AND_PROPAGATE. When HuggingFace returnsX-Error-Message: "Access to this resource is disabled."(moderation takedown), the chain now advances to the next source rather than immediately propagatingDisabledRepoError. The aggregate response surfaces thedisabledmarker only when every source in the chain returns disabled.huggingface_hubclients are unaffected — they don't inspect chain internals — but tooling that depended on first-sourcedisabledpropagating verbatim must update. (#77) - Fallback:
with_repo_fallbackdecorator gates the 404 fall-through on the local response'sX-Error-Code. A local 404 carryingEntryNotFoundorRevisionNotFoundis now authoritative ("the local repo exists; this entry/revision is missing") and is not dispatched to the fallback chain. The chain is entered only when the local layer signalsRepoNotFoundor returns a 404 withoutX-Error-Code. Restores the user-stated guarantee that a local repo wins absolutely on its namespace, regardless of upstream state. (#77)
Tracked
Planned follow-up work surfaced during the #77 risk review:
- #78 — lower default
fallback cache TTL (
KOHAKU_HUB_FALLBACK_CACHE_TTL) from300to60, decouple chain-probe logic into a purecore.probe_chainfunction, and add admin endpoints + frontend panel for real / simulated chain testing.