diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,24 @@
 
 ## Unreleased
 
+## 0.1.2.3 — 2026-08-29
+
+### Changed
+
+- Every library dependency now carries a PVP upper bound: `aeson`, `blake3`,
+  `bytestring`, `containers`, `direct-sqlite`, `effectful`, `generic-lens`,
+  `scientific`, `stm`, `time`, `vector`. `cabal check` reported these under
+  `missing-upper-bounds`. Without one, a future breaking release of a dependency
+  enters a consumer's build plan unchecked — which is the failure the bound
+  exists to prevent.
+
+  Each bound admits the version this package is built and tested against and
+  stops below the next major.
+
+  `aeson` stops at `<2.3` rather than `<2.4`: baikai-openai 0.5 constrains it to
+  `^>=2.2`, so aeson 2.3 is not reachable for this cohort and a wider bound
+  would assert compatibility that cannot be exercised here.
+
 ## 0.1.2.2 — 2026-08-07
 
 ### Changed
diff --git a/shikumi-cache.cabal b/shikumi-cache.cabal
--- a/shikumi-cache.cabal
+++ b/shikumi-cache.cabal
@@ -1,6 +1,6 @@
 cabal-version:   3.4
 name:            shikumi-cache
-version:         0.1.2.2
+version:         0.1.2.3
 synopsis:        Content-addressed response caching for shikumi (EP-6)
 category:        AI
 description:
@@ -44,22 +44,22 @@
     Shikumi.Cache.Types
 
   build-depends:
-    , aeson
-    , baikai         >=0.5      && <0.6
+    , aeson          >=2.2      && <2.3
+    , baikai         >=0.6      && <0.7
     , base           >=4.20     && <5
-    , blake3
-    , bytestring
-    , containers
-    , direct-sqlite
-    , effectful
-    , generic-lens
+    , blake3         >=0.3      && <0.4
+    , bytestring     >=0.11     && <0.13
+    , containers     >=0.6      && <0.9
+    , direct-sqlite  >=2.3      && <2.4
+    , effectful      >=2.5      && <2.7
+    , generic-lens   >=2.2      && <2.4
     , lens           ^>=5.3
-    , scientific
+    , scientific     >=0.3      && <0.4
     , shikumi        ^>=0.3.0.0
-    , stm
+    , stm            >=2.5      && <2.6
     , text           ^>=2.1
-    , time
-    , vector
+    , time           >=1.12     && <1.17
+    , vector         >=0.13     && <0.14
 
 test-suite shikumi-cache-test
   import:         common-options
@@ -69,7 +69,7 @@
   ghc-options:    -threaded -with-rtsopts=-N
   build-depends:
     , aeson
-    , baikai         >=0.5      && <0.6
+    , baikai         >=0.6      && <0.7
     , base
     , bytestring
     , containers
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -20,12 +20,12 @@
     Response,
     StopReason (ErrorReason),
     defaultAnthropicMessagesCompat,
+    emptyContext,
+    emptyModel,
+    emptyOptions,
+    emptyResponse,
     user,
     userAt,
-    _Context,
-    _Model,
-    _Options,
-    _Response,
   )
 import Control.Exception (bracket)
 import Control.Lens ((&), (.~))
@@ -71,16 +71,16 @@
 -- ---------------------------------------------------------------------------
 
 fixModel :: Model
-fixModel = _Model & #modelId .~ "claude-sonnet-4-6" & #provider .~ "anthropic"
+fixModel = emptyModel & #modelId .~ "claude-sonnet-4-6" & #provider .~ "anthropic"
 
 fixCtx :: Context
 fixCtx =
-  _Context
+  emptyContext
     & #systemPrompt .~ Just "You are helpful."
     & #messages .~ V.singleton (user "ping")
 
 fixOpts :: Options
-fixOpts = _Options & #temperature .~ Just 0.0 & #maxTokens .~ Just 1024
+fixOpts = emptyOptions & #temperature .~ Just 0.0 & #maxTokens .~ Just 1024
 
 -- | The pinned cache key for @(fixModel, fixCtx, fixOpts)@ — the value EP-7 must
 -- reproduce. Captured from a first run; any drift in field set, canonical JSON,
@@ -89,7 +89,7 @@
 pinnedKey = "b31fd70140abbd0198c6b7caec748a8389bf93be909164bdcc340731b7032564"
 
 stubResponse :: Response
-stubResponse = _Response
+stubResponse = emptyResponse
 
 someTime :: UTCTime
 someTime = read "2026-06-08 00:00:00 UTC"
