Skip to content

fix(cache): persist offline bundled fallback so format-stale caches don't rebuild every call (#140)#142

Merged
thomaschristory merged 2 commits into
mainfrom
fix/140-offline-format-stale-cache-rebuild
Jul 3, 2026
Merged

fix(cache): persist offline bundled fallback so format-stale caches don't rebuild every call (#140)#142
thomaschristory merged 2 commits into
mainfrom
fix/140-offline-format-stale-cache-rebuild

Conversation

@thomaschristory

Copy link
Copy Markdown
Owner

Closes #140.

Problem

Follow-up from #139 (v1.6.2), which added CommandModel.format_version and made CacheStore.load() reject entries older than MODEL_FORMAT_VERSION so a one-time rebuild picks up new model metadata after an upgrade.

The online path self-heals immediately. But when the only cached entry is format-stale-but-hash-fresh and NetBox is unreachable:

  • _find_any_cached rejects the stale entry, the fetch fails, and resolution falls to _load_bundled_command_model() — rebuilding from the bundled schema (~180ms) on every invocation, because nothing re-saved on the offline branch.
  • completion/cache_probe has no rebuild fallback, so tab-completion returns nothing for that profile until the first online command rewrites the cache — a brief completion blackout.

Bounded and self-healing (no crash, no loop), but wasteful. Found by adversarial review of #139.

Fix

Persist the bundled fallback under the profile via a new CacheStore.save(..., record_fetch=False):

  • No per-invocation rebuild — the next offline call hits _find_any_cached (cheap JSON load) instead of re-parsing the bundled schema.
  • No completion blackoutcache_probe now surfaces a format-current entry.
  • Still self-heals — because no fetched_at sidecar is written, the TTL fast-path keeps distrusting the entry and attempts a real fetch once NetBox is reachable; _build_and_cache then saves under the live hash.
  • Write failure on the fallback path is non-fatal (contextlib.suppress(OSError)); the in-memory model is still returned.

Tests

  • test_save_without_record_fetch_writes_no_sidecar — store-level: model persisted, no sidecar, load_fetched_at is None.
  • test_offline_format_stale_cache_persists_bundled_fallback — end-to-end: stale entry + offline → bundled persisted (distinct hash, no timestamp, visible to cache_probe); second offline call served from _find_any_cached ("using cached schema").

Full suite (1364 passed), ruff, and mypy --strict all green. Version bumped to 1.6.3 with CHANGELOG entry.

🤖 Generated with Claude Code

…on't rebuild every call

When the only cached command-model is format-stale (rejected by the
format_version gate added in #139) and NetBox is unreachable, resolution fell
back to the bundled schema and rebuilt it (~180ms) on every invocation, and
tab-completion returned nothing for the profile until the first online command.

Persist the bundled fallback under the profile via CacheStore.save(
record_fetch=False) — no fetch timestamp, so the TTL fast-path still refetches
once NetBox is reachable, but the next offline invocation hits _find_any_cached
and completion surfaces a format-current entry immediately.

Bumps version to 1.6.3.

Closes #140

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_validate_profile raises ValueError (not OSError) for a profile name outside
_PROFILE_RE — and names aren't validated at config time. Suppress it too so the
offline bundled fallback honors its non-fatal contract and still returns the
in-memory model. Adversarial-review finding on #142.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thomaschristory

Copy link
Copy Markdown
Owner Author

Adversarial review pass done (feature-dev:code-reviewer). Core claims verified: self-heals online, no-sidecar entry never served as fresh, no rebuild on repeat offline calls, completion recovers. One sub-threshold finding applied — _persist_offline_fallback now also suppresses ValueError from _validate_profile so the non-fatal contract holds for odd-but-legal profile names; added a regression test.

@thomaschristory thomaschristory merged commit 7d0931c into main Jul 3, 2026
7 checks passed
@thomaschristory thomaschristory deleted the fix/140-offline-format-stale-cache-rebuild branch July 3, 2026 20:44
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.

Cache: format-version invalidation forces rebuild-from-bundled every invocation when offline

1 participant