diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # Changelog
 
+## 0.1.8.0 — 2026-03-08
+
+### Builder Correctness, Windows Store Paths, Hackage Fix
+
+- **Fix #1: builder no longer pre-creates output paths** — `buildDerivationInner` no longer calls `createDirectoryIfMissing` for `$out`, `$dev`, etc. before running the builder. The builder is now responsible for creating its own outputs, matching real Nix semantics. Builds fail with `"builder succeeded but outputs missing"` if the builder exits successfully but expected outputs don't exist.
+- **File outputs supported** — `$out` can now be a regular file, not just a directory. `registerSingleOutput`, `addToStore`, `scanReferences`, and `pathExists` all use `doesPathExist` instead of `doesDirectoryExist`. `moveOutput` (renamed from `moveDirectory`) handles both files and directories in cross-device fallback. `collectRegularFiles` accepts file paths directly for reference scanning.
+- **Fix #2: Windows store paths use native separators** — CLI now uses `platformStoreDir` (`C:\nix\store` on Windows, `/nix/store` on Unix) for filesystem operations and display. Evaluator internals keep canonical `/nix/store` for ATerm hash compatibility.
+- **Fix: `crypton < 1.1` in .cabal file** — Previous `crypton` pin was only in `cabal.project` (which Hackage ignores). Moved to `.cabal` so the Hackage solver respects it. `crypton >= 1.1` switched from `memory` to `ram` for `ByteArrayAccess`, breaking `http-client-tls`.
+- 108 builtins, 526 tests, -Werror clean, ormolu clean, hlint clean
+
 ## 0.1.7.1 — 2026-03-07
 
 ### Hackage Build Fix
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -260,7 +260,7 @@
 **Key numbers:**
 
 - **24 modules** — all implemented
-- **524 tests** — hand-rolled harness, no framework dependencies
+- **526 tests** — hand-rolled harness, no framework dependencies
 - **Zero partial functions** — total by construction, `T.uncons` over `T.head`/`T.tail`
 - **Strict by default** — bang patterns on all data fields (except Thunk's Env, which is lazy for knot-tying)
 
@@ -306,7 +306,7 @@
 
 ```bash
 cabal build                              # Build library + CLI
-cabal test                               # Run all 524 tests
+cabal test                               # Run all 526 tests
 cabal build --ghc-options="-Werror"      # Warnings as errors (CI default)
 cabal haddock                            # Generate API docs
 ```
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -27,7 +27,7 @@
 import Nix.Eval.IO (EvalState (..), newEvalState, runEvalIO)
 import Nix.Parser (parseNix, readFileAutoEncoding)
 import Nix.Store (Store, closeStore, openStore, writeDrv)
-import Nix.Store.Path (StorePath, defaultStoreDir, parseStorePath, storePathToFilePath)
+import Nix.Store.Path (StorePath, defaultStoreDir, parseStorePath, platformStoreDir, storePathToFilePath)
 import Paths_nova_nix (getDataDir)
 import System.Directory (getCurrentDirectory, getTemporaryDirectory)
 import System.Environment (getArgs)
@@ -165,12 +165,12 @@
           exitFailure
         Right val -> do
           (drv, drvSP) <- extractDerivation val
-          store <- openStore defaultStoreDir
+          store <- openStore platformStoreDir
           buildResult <- buildAndRegister store drv drvSP
           closeStore store
           case buildResult of
             BuildSuccess sp ->
-              TIO.putStrLn (T.pack (storePathToFilePath defaultStoreDir sp))
+              TIO.putStrLn (T.pack (storePathToFilePath platformStoreDir sp))
             BuildFailure msg code -> do
               TIO.hPutStrLn stderr ("build failed (exit " <> T.pack (show code) <> "): " <> msg)
               exitFailure
@@ -219,7 +219,7 @@
   -- Build with dependency resolution
   tmpDir <- getTemporaryDirectory
   let config =
-        (defaultBuildConfig defaultStoreDir)
+        (defaultBuildConfig platformStoreDir)
           { bcTmpDir = tmpDir
           }
   buildWithDeps config store drv drvSP
@@ -272,7 +272,7 @@
 prettyValue (VCompiledRegex _) = "«compiled-regex»"
 prettyValue (VDerivation drv) =
   case drvOutputs drv of
-    (out : _) -> "«derivation " <> T.pack (storePathToFilePath defaultStoreDir (doPath out)) <> "»"
+    (out : _) -> "«derivation " <> T.pack (storePathToFilePath platformStoreDir (doPath out)) <> "»"
     [] -> "«derivation»"
 
 -- | Pretty-print a thunk.  After deep-forcing, all thunks should be
diff --git a/nova-nix.cabal b/nova-nix.cabal
--- a/nova-nix.cabal
+++ b/nova-nix.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               nova-nix
-version:            0.1.7.1
+version:            0.1.8.0
 synopsis:           Windows-native Nix implementation in pure Haskell
 description:
   A pure Haskell implementation of the Nix package manager that runs natively
@@ -63,7 +63,7 @@
     , array               >= 0.5 && < 0.6
     , bytestring          >= 0.11 && < 0.13
     , containers          >= 0.6 && < 0.8
-    , crypton             >= 1.0 && < 2
+    , crypton             >= 1.0 && < 1.1
     , directory           >= 1.3 && < 1.4
     , filepath            >= 1.4 && < 1.6
     , http-client         >= 0.7 && < 0.8
diff --git a/src/Nix/Builder.hs b/src/Nix/Builder.hs
--- a/src/Nix/Builder.hs
+++ b/src/Nix/Builder.hs
@@ -43,7 +43,7 @@
 where
 
 import Control.Exception (SomeException, try)
-import Control.Monad (when)
+import Control.Monad (filterM, when)
 import Data.Char (toLower)
 import Data.Map.Strict (Map)
 import qualified Data.Map.Strict as Map
@@ -56,7 +56,7 @@
 import Nix.Store (Store (..), addToStore, isValid, scanReferences)
 import Nix.Store.Path (StoreDir (..), StorePath (..), storePathToFilePath)
 import Nix.Substituter (CacheConfig, SubstResult (..), trySubstitute)
-import System.Directory (createDirectoryIfMissing, doesDirectoryExist, removeDirectoryRecursive)
+import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesPathExist, removeDirectoryRecursive)
 import qualified System.Environment
 import System.Exit (ExitCode (..))
 import System.FilePath (takeDirectory, takeFileName, (</>))
@@ -155,9 +155,9 @@
       let buildDir = computeBuildDir config drv
       createDirectoryIfMissing True buildDir
 
-      -- 3. Create output directories inside build dir
+      -- 3. Compute output paths (but do NOT pre-create them — the builder
+      --    is responsible for creating $out, $dev, etc.)
       let outputDirs = [(doName out, buildDir </> T.unpack (doName out)) | out <- drvOutputs drv]
-      mapM_ (createDirectoryIfMissing True . snd) outputDirs
 
       -- 4. Set up environment
       let builderPath = T.unpack (drvBuilder drv)
@@ -172,11 +172,19 @@
           cleanupBuildDir buildDir
           pure (BuildFailure ("builder failed: " <> stderrText) exitCode)
         Right () -> do
-          -- 7. Success: register each output
-          registerResult <- registerOutputs config store drv buildDir outputDirs
-          -- Clean up build dir
-          cleanupBuildDir buildDir
-          pure registerResult
+          -- 7. Success: validate that the builder created all expected outputs.
+          --    Outputs may be files or directories — both are valid (real Nix
+          --    allows $out to be a single file, a directory tree, or a symlink).
+          missing <- filterM (fmap not . doesPathExist . snd) outputDirs
+          case missing of
+            [] -> do
+              registerResult <- registerOutputs config store drv buildDir outputDirs
+              cleanupBuildDir buildDir
+              pure registerResult
+            _ -> do
+              cleanupBuildDir buildDir
+              let names = T.intercalate ", " (map fst missing)
+              pure (BuildFailure ("builder succeeded but outputs missing: " <> names) 1)
 
 -- ---------------------------------------------------------------------------
 -- Input validation
@@ -398,15 +406,15 @@
 registerSingleOutput config store candidates drvPathText (output, (_outName, outDir)) = do
   let targetSP = doPath output
       targetPath = storePathToFilePath (bcStoreDir config) targetSP
-  -- Check if output directory exists (builder should have created it)
-  exists <- doesDirectoryExist outDir
+  -- Check if output exists (file or directory — builder creates it)
+  exists <- doesPathExist outDir
   if not exists
-    then pure (Left ("output directory missing: " <> T.pack outDir))
+    then pure (Left ("output missing: " <> T.pack outDir))
     else do
       -- Scan for references in the output
       refs <- scanReferences (bcStoreDir config) candidates outDir
       -- Check if already in store (e.g. from a previous build)
-      alreadyExists <- doesDirectoryExist targetPath
+      alreadyExists <- doesPathExist targetPath
       if alreadyExists
         then pure (Right ())
         else do
diff --git a/src/Nix/Store.hs b/src/Nix/Store.hs
--- a/src/Nix/Store.hs
+++ b/src/Nix/Store.hs
@@ -69,8 +69,10 @@
     createDirectoryIfMissing,
     doesDirectoryExist,
     doesFileExist,
+    doesPathExist,
     listDirectory,
     removeDirectoryRecursive,
+    removeFile,
     renamePath,
     setPermissions,
   )
@@ -99,18 +101,18 @@
 isValid :: Store -> StorePath -> IO Bool
 isValid = isValidPath . stDB
 
--- | Check if a store path directory exists on disk (regardless of DB).
+-- | Check if a store path exists on disk (file or directory, regardless of DB).
 pathExists :: Store -> StorePath -> IO Bool
-pathExists store sp = doesDirectoryExist (storePathToFilePath (stDir store) sp)
+pathExists store sp = doesPathExist (storePathToFilePath (stDir store) sp)
 
 -- ---------------------------------------------------------------------------
 -- Store operations
 -- ---------------------------------------------------------------------------
 
--- | Move an output directory to the store path, set read-only, and register.
+-- | Move a build output (file or directory) to the store path, set read-only,
+-- and register.
 --
--- If @renameDirectory@ fails (cross-device move), falls back to
--- recursive copy + remove.
+-- If @renamePath@ fails (cross-device move), falls back to copy + remove.
 addToStore ::
   Store ->
   FilePath ->
@@ -118,10 +120,10 @@
   Maybe Text ->
   [StorePath] ->
   IO ()
-addToStore store srcDir sp deriver refs = do
+addToStore store srcPath sp deriver refs = do
   let destPath = storePathToFilePath (stDir store) sp
   -- Move (or copy) source to store
-  moveDirectory srcDir destPath
+  moveOutput srcPath destPath
   -- Set read-only permissions
   setReadOnly destPath
   -- Register in database
@@ -135,13 +137,19 @@
         prReferences = refs
       }
 
--- | Cross-device safe directory move.
+-- | Cross-device safe move for files or directories.
 -- Tries 'renamePath' first; on IOException falls back to copy + remove.
-moveDirectory :: FilePath -> FilePath -> IO ()
-moveDirectory src dest =
+moveOutput :: FilePath -> FilePath -> IO ()
+moveOutput src dest =
   renamePath src dest `catch` \(_ :: IOException) -> do
-    copyDirectoryRecursive src dest
-    removeDirectoryRecursive src
+    isDir <- doesDirectoryExist src
+    if isDir
+      then do
+        copyDirectoryRecursive src dest
+        removeDirectoryRecursive src
+      else do
+        copyFile src dest
+        removeFile src
 
 -- | Recursively copy a directory tree.
 copyDirectoryRecursive :: FilePath -> FilePath -> IO ()
@@ -176,15 +184,18 @@
     pure (scanBytes prefixBytes prefixLen hashLen contents acc)
   pure [sp | (h, sp) <- Set.toList candidateSet, Set.member h foundHashes]
 
--- | Collect all regular files under a directory, recursively.
+-- | Collect all regular files under a path, recursively.
+-- If the path is itself a regular file, returns it directly.
 collectRegularFiles :: FilePath -> IO [FilePath]
-collectRegularFiles dir = do
-  exists <- doesDirectoryExist dir
-  if not exists
-    then pure []
+collectRegularFiles path = do
+  isDir <- doesDirectoryExist path
+  if isDir
+    then do
+      entries <- listDirectory path
+      concat <$> mapM (classifyAndCollect path) entries
     else do
-      entries <- listDirectory dir
-      concat <$> mapM (classifyAndCollect dir) entries
+      isFile <- doesFileExist path
+      pure [path | isFile]
   where
     classifyAndCollect parent name = do
       let fullPath = parent </> name
diff --git a/src/Nix/Store/Path.hs b/src/Nix/Store/Path.hs
--- a/src/Nix/Store/Path.hs
+++ b/src/Nix/Store/Path.hs
@@ -33,6 +33,7 @@
     StoreDir (..),
     defaultStoreDir,
     defaultStoreDirText,
+    platformStoreDir,
     platformStoreDirText,
     windowsStoreDir,
 
@@ -64,15 +65,20 @@
 defaultStoreDirText :: Text
 defaultStoreDirText = T.pack (unStoreDir defaultStoreDir)
 
+-- | Platform-appropriate store directory.
+-- Returns @C:\\nix\\store@ on Windows, @\/nix\/store@ on Unix.
+-- Use this for filesystem operations and user-facing output.
+-- Use 'defaultStoreDir' only for Nix-internal canonical paths (ATerm hashing).
+platformStoreDir :: StoreDir
+platformStoreDir = case System.Info.os of
+  "mingw32" -> windowsStoreDir
+  _ -> defaultStoreDir
+
 -- | Platform-appropriate store directory as 'Text'.
 -- Returns @C:\\nix\\store@ on Windows, @\/nix\/store@ on Unix.
 -- Used for user-facing values like @builtins.storeDir@.
 platformStoreDirText :: Text
 platformStoreDirText = T.pack (unStoreDir platformStoreDir)
-  where
-    platformStoreDir = case System.Info.os of
-      "mingw32" -> windowsStoreDir
-      _ -> defaultStoreDir
 
 -- | Default store directory on Windows: @C:\\nix\\store@.
 windowsStoreDir :: StoreDir
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -3037,6 +3037,41 @@
         forceRemoveIfExists tmpStore
         forceRemoveIfExists (bcTmpDir config)
         pure ret,
+      -- Builder succeeds but doesn't create $out -> build fails
+      runTestM "missing output fails build" $ do
+        tmpBase <- getTemporaryDirectory
+        let tmpStore = tmpBase </> "nova-nix-test-builder-noout"
+        forceRemoveIfExists tmpStore
+        store <- openStore (StoreDir tmpStore)
+        let outSP = StorePath "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" "nooutput"
+            drv = mkTestBuildDrv shell outSP "echo 'forgot to create output'"
+            config = (defaultBuildConfig (stDir store)) {bcTmpDir = tmpBase </> "nova-nix-test-builder-noout-tmp"}
+        result <- buildDerivation config store drv
+        closeStore store
+        forceRemoveIfExists tmpStore
+        forceRemoveIfExists (bcTmpDir config)
+        pure $ case result of
+          BuildFailure msg _ ->
+            if T.isInfixOf "outputs missing" msg
+              then Pass
+              else Fail ("expected 'outputs missing' error, got: " <> msg)
+          BuildSuccess _ -> Fail "expected failure when builder doesn't create $out",
+      -- File output (not directory) should succeed
+      runTestM "file output succeeds" $ do
+        tmpBase <- getTemporaryDirectory
+        let tmpStore = tmpBase </> "nova-nix-test-builder-fileout"
+        forceRemoveIfExists tmpStore
+        store <- openStore (StoreDir tmpStore)
+        let outSP = StorePath "jjjjjjjjjjjjjjjjjjjjjjjjjjjjjj" "fileout"
+            drv = mkTestBuildDrv shell outSP "echo 'I am a file output' > $out"
+            config = (defaultBuildConfig (stDir store)) {bcTmpDir = tmpBase </> "nova-nix-test-builder-fileout-tmp"}
+        result <- buildDerivation config store drv
+        closeStore store
+        forceRemoveIfExists tmpStore
+        forceRemoveIfExists (bcTmpDir config)
+        pure $ case result of
+          BuildSuccess sp -> assertEqual "file output path" outSP sp
+          BuildFailure msg code -> Fail ("file output build failed (" <> T.pack (show code) <> "): " <> msg),
       -- Cleanup after failure
       runTestM "cleanup after failure" $ do
         tmpBase <- getTemporaryDirectory
