diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,28 @@
 # Changelog
 
+## 0.3.2.0 — 2026-03-18
+
+### Seed action fixes
+
+- **Resolve runtime outputs, not derivations** — `nix-instantiate` replaced
+  with `nix-build --no-out-link` so the cache stores actual binaries instead of
+  `.drv` build recipes.
+- **Filter uploads to diff-only** — `nix copy` exports transitive deps; uploads
+  now filtered against the missing-hashes diff to avoid re-uploading cached paths.
+- **Fix broken pipe** — `find | xargs` with `pipefail` caused spurious failures;
+  now writes to intermediate files.
+- **Fix xargs line-too-long** — large path lists passed via file instead of inline.
+- **Diagnostic output** — upload failure HTTP codes now printed; `nix copy` errors
+  no longer silenced.
+- **Parallelism** — default reduced from 32 to 8; per-upload `--max-time` added.
+
+### Server validation
+
+- **Reject derivation narinfos** — `validateNarInfo` now rejects any narinfo
+  where StorePath ends in `.drv` with a new `DerivationStorePath` error. Binary
+  caches serve build outputs, not build recipes.
+- 1 new test (75 total)
+
 ## 0.3.1.0 — 2026-03-07
 
 ### Drop `memory` dependency, use `ram`
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -295,7 +295,7 @@
 ```
 
 - **10 modules**, 8 pure + 2 at the IO boundary (Compression optional via flag)
-- **74 core tests + 4 compression tests**, hand-rolled harness, no framework dependencies
+- **75 core tests + 4 compression tests**, hand-rolled harness, no framework dependencies
 - **Zero partial functions** — total by construction
 - **Strict by default** — bang patterns on all data fields
 
@@ -403,9 +403,9 @@
 | `cache-url` | yes | Base URL of the nova-cache server |
 | `api-key` | yes | Bearer token for authenticating uploads |
 | `paths` | no | Explicit store paths (space-separated). Defaults to all paths from `shell.nix` / `default.nix` |
-| `parallel` | no | Max concurrent uploads (default: 32) |
+| `parallel` | no | Max concurrent uploads (default: 8) |
 
-The action fetches `GET /narinfo-hashes` to determine which paths are already cached, exports only the missing paths, and uploads narinfo + NAR files in parallel. Falls back to uploading everything if the endpoint is unavailable (servers < 0.3.0.0). Works with any CI that has Nix installed.
+The action resolves runtime store paths via `nix-build`, diffs against the server's `GET /narinfo-hashes`, exports only missing paths, and uploads narinfo + NAR files in parallel. Includes per-upload timeouts, failure diagnostics, and a round-trip validation check. Works with any CI that has Nix installed.
 
 ---
 
diff --git a/nova-cache.cabal b/nova-cache.cabal
--- a/nova-cache.cabal
+++ b/nova-cache.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               nova-cache
-version:            0.3.1.0
+version:            0.3.2.0
 synopsis:           Pure Nix binary cache protocol library
 description:
   A focused, minimal, pure-first library implementing the full Nix binary
diff --git a/src/NovaCache/Validate.hs b/src/NovaCache/Validate.hs
--- a/src/NovaCache/Validate.hs
+++ b/src/NovaCache/Validate.hs
@@ -15,6 +15,7 @@
 
 import Data.ByteString (ByteString)
 import Data.Text (Text)
+import qualified Data.Text as T
 import NovaCache.Hash (formatNixHash, hashBytes, parseNixHash)
 import NovaCache.NarInfo (NarInfo (..))
 import NovaCache.Signing (PublicKey, verify)
@@ -46,6 +47,8 @@
     SignatureInvalid !Text
   | -- | The narinfo has zero signatures.
     NoSignatures
+  | -- | StorePath is a derivation (.drv) — binary caches serve build outputs only.
+    DerivationStorePath !Text
   deriving (Eq, Show)
 
 -- ---------------------------------------------------------------------------
@@ -57,13 +60,16 @@
 -- Returns the 'NarInfo' unchanged on success for composition.
 validateNarInfo :: NarInfo -> Either [ValidationError] NarInfo
 validateNarInfo ni =
-  case concat [sizeErrors, storePathErrors, fileHashErrors, narHashErrors, refErrors] of
+  case concat [sizeErrors, drvErrors, storePathErrors, fileHashErrors, narHashErrors, refErrors] of
     [] -> Right ni
     errs -> Left errs
   where
     sizeErrors =
       [NegativeFileSize (niFileSize ni) | niFileSize ni < 0]
         ++ [NegativeNarSize (niNarSize ni) | niNarSize ni < 0]
+
+    drvErrors =
+      [DerivationStorePath (niStorePath ni) | ".drv" `T.isSuffixOf` niStorePath ni]
 
     storePathErrors = case parseStorePath defaultStoreDir (niStorePath ni) of
       Left err -> [InvalidStorePath (niStorePath ni) err]
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -631,6 +631,15 @@
               "negative narsize"
               (Left [Validate.NegativeNarSize (-1)])
               (Validate.validateNarInfo ni),
+      test "validateNarInfo derivation StorePath rejected" $
+        let drvPath = "/nix/store/abc12345678901234567890123456789-foo.drv"
+            ni = mkValidNarInfo {NarInfo.niStorePath = drvPath}
+         in case Validate.validateNarInfo ni of
+              Left [Validate.DerivationStorePath raw] ->
+                assertEqual "raw value" drvPath raw
+              other -> do
+                putStrLn ("    expected Left [DerivationStorePath ..], got: " ++ show other)
+                pure False,
       test "validateNarInfo bad StorePath" $
         let ni = mkValidNarInfo {NarInfo.niStorePath = "not-a-store-path"}
          in case Validate.validateNarInfo ni of
