diff --git a/.claude/settings.json b/.claude/settings.json index adf9d29..a7edabe 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -2,31 +2,38 @@ "attribution": { "commit": "", "pr": "" - }, + }, "permissions": { "allow": [ "Bash(cd:*)", "Bash(dotnet:*)", + "Bash(dotnet test:*)", "Bash(mkdir:*)", + "Bash(rmdir:*)", "Bash(cp:*)", + "Bash(mv:*)", "Bash(rm:*)", "Bash(ls:*)", "Bash(cat:*)", - "Skill(update-config)", - "Skill(update-config:*)", "Bash(xargs grep:*)", - "Bash(dotnet test:*)", "Bash(gh pr:*)", "Bash(gh run:*)", + "Bash(gh api:*)", + "Bash(gh issue *)", + "Bash(gh repo *)", "Bash(git checkout:*)", "Bash(git add:*)", "Bash(git commit:*)", "Bash(git merge:*)", "Bash(git rm:*)", "Bash(git branch:*)", - "Bash(git log:*)" + "Bash(git log:*)", + "Bash(git push:*)", + "Bash(git fetch:*)", + "Bash(git pull:*)", + "Skill(update-config)", + "Skill(update-config:*)" ], - "additionalDirectories": [ - ] + "additionalDirectories": [] } } diff --git a/plan/feature.md b/plan/feature.md deleted file mode 100644 index e371e58..0000000 --- a/plan/feature.md +++ /dev/null @@ -1,46 +0,0 @@ -# Feature: Fix AddCache singleton options capture - -## Goal - -`AddCache` is intended to support being called multiple times — its own `AppendPreviousRegistrations` helper plus the `RemoveAll>` + re-add pattern documents that intent. In practice the second call's registrations are silently lost because the singleton factories close over the local `CacheOptions o` instead of resolving `IOptions` from the service provider. The first call's `o` is the one bound to every singleton, regardless of what later calls register. - -Switch every affected factory to resolve `IOptions` from the SP. Singletons are constructed lazily on first resolution, after composition is complete, so they pick up the final merged options. - -## Source - -Eplicta request (2026-04-14, High) in `$DOC_ROOT/Tharga/Requests.md`. Reproduces in any app that combines its own `AddCache` with `AddQuilt4NetApplicationInsightsClient`, which calls `AddCache` transitively. Eplicta observes 23 production exceptions on `EnvironmentOption[]`: `InvalidOperationException: "No freshSpan provided and no DefaultFreshSpan configured for cache type ..."` even though the type *was* registered in the second call — just not in the `o` the first call's closure captured. - -## Scope - -In `Tharga.Cache/CacheRegistrationExtensions.cs`, the 6 factories that close over `o`: - -1. `IFetchQueue` (line 35) — uses `o.MaxConcurrentFetchCount` -2. `IManagedCacheMonitor` (line 42) — passes `o` to `CacheMonitor` -3. `IEternalCache` (line 60) -4. `ITimeToLiveCache` (line 67) -5. `ITimeToIdleCache` (line 74) -6. `IScopeCache` (line 81) — scoped, but same closure bug applies - -Note: the Eplicta request only enumerated the 4 cache types. The two infrastructure singletons (`IFetchQueue`, `IManagedCacheMonitor`) have the same closure-capture bug and need the same fix for consistency. Without this they would still see first-call options if `MaxConcurrentFetchCount` / `WatchDogInterval` ever change between calls. - -Out of scope: -- Public API changes -- `RegisterAllIPersistImplementations`, `InvokeAllPersistRegistrations` — these run eagerly during `AddCache` itself, so reading the local `o` is correct. -- `o.RegisterConfigurations(serviceCollection)` callbacks — eager, local `o` is correct. -- Tharga.Console 3.7.4 → 4.0.0 upgrade in `Tharga.Cache.Console` (deferred to a separate change). - -## Acceptance criteria - -- All 6 factories resolve `IOptions` from the SP. No factory closes over the local `o`. -- New regression test in `AddCacheIdempotencyTests.cs`: call `AddCache` twice with different `RegisterType<>` calls, build the SP, resolve a cache, and prove the **second** call's registrations are honored (e.g. `DefaultFreshSpan` set in the second call works at runtime). -- All existing tests in `Tharga.Cache.Tests` still pass. -- `Tharga.Cache.csproj` version bumped: `1.0.0` → `1.1.0` (minor; user-chosen). -- README updated only if it documents one-call-only registration (check `Tharga.Cache/README.md` for guidance about `AddCache` call count). - -## Done condition - -- Feature branch `feature/fix-addcache-options-capture` pushed -- PR opened from `feature/fix-addcache-options-capture` → `master` (GitHub Actions branching strategy) -- CI green -- User confirms the fix as done -- Eplicta workaround referenced in `Core/Eplicta.Aggregator/Program.cs` (per the Requests.md note) can be removed once the new `Tharga.Cache` package version is consumed — tracked as follow-up in `Requests.md` diff --git a/plan/plan.md b/plan/plan.md deleted file mode 100644 index 2a1cd0c..0000000 --- a/plan/plan.md +++ /dev/null @@ -1,41 +0,0 @@ -# Plan: Fix AddCache singleton options capture - -## Steps - -- [x] **1. Write the regression test first (red)** - Added `AddCache_CalledTwice_SecondCallRegistrationHonoredAtRuntime` to `AddCacheIdempotencyTests.cs`. Calls `AddCache` twice with `RegisterType` then `RegisterType`, both with `DefaultFreshSpan`. Resolves `ITimeToLiveCache`, calls `GetAsync` with no explicit freshSpan, asserts it does not throw. - -- [x] **2. Confirmed test fails on master** - Failed with the exact Eplicta-reported exception: `InvalidOperationException: No freshSpan provided and no DefaultFreshSpan configured for cache type Int32` at `TimeCacheBase.GetDefaultFreshSpan[T]()`. Bug reproduced. - -- [x] **3. Fix the 6 factories in `CacheRegistrationExtensions.AddCache`** - All factories now call `var opts = s.GetRequiredService>().Value;` and pass `opts` instead of the captured `o`. Fixed: `IFetchQueue`, `IManagedCacheMonitor`, `IEternalCache`, `ITimeToLiveCache`, `ITimeToIdleCache`, `IScopeCache`. - -- [x] **4. Run the test suite** - - `Tharga.Cache.Tests`: 474/474 pass (regression test included, plus one re-run for a flaky time-critical concurrency test unrelated to this fix). - - `Tharga.Cache.Redis.Tests`: 2/2 pass. - - `Tharga.Cache.File.Tests`: 2 skipped (file-infra-dependent). - - `Tharga.Cache.MongoDB.Tests`: no tests discovered (integration-only project). - -- [x] **5. Check README for one-call guidance** - Grepped `README.md` and `Tharga.Cache/README.md` — no text suggesting `AddCache` must be called once. No README change needed. - -- [x] **6. Bump version `Tharga.Cache.csproj` 1.0.0 → 1.1.0** - Done. - -- [~] **7. Commit + push** - Conventional commit: `fix: resolve CacheOptions from DI in singleton factories so multi-call AddCache merges correctly`. Push branch. - -- [ ] **8. Open PR `feature/fix-addcache-options-capture` → `master`** - -- [ ] **9. Wait for CI green; hand to user** - Do not merge — PR is the review gate. User to confirm done, then per shared-instructions: archive `plan/feature.md` to `$DOC_ROOT/Tharga/plans/Toolkit/Cache/done/fix-addcache-options-capture.md`, delete `plan/`, mark the Eplicta request `Status: Done` with completion date and add the follow-up entry in `Requests.md` (Eplicta should bump Tharga.Cache to 1.1.0 and remove their workaround in `Core/Eplicta.Aggregator/Program.cs`). - -## Notes - -- Scope was expanded from the 4 caches Eplicta listed to all 6 factories. `IFetchQueue` (reads `MaxConcurrentFetchCount`) and `IManagedCacheMonitor` (passes `CacheOptions` to `CacheMonitor`) have the same closure-capture bug and were fixed for consistency. -- First-call-wins on type registrations preserved (`AppendPreviousRegistrations` uses `TryAdd`). Only the singleton-options binding changed — singletons now lazily resolve `IOptions` from the SP, picking up the merged view from the last `AddCache` call. - -## Last session - -Implementation complete on `feature/fix-addcache-options-capture`. All 6 factories rewritten, regression test added and passing, version bumped to 1.1.0. Pending: commit, push, open PR, wait for CI, user confirmation, then archive plan + close Eplicta request with follow-up.