diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,28 @@
+# [0.2.0.0](https://github.com/haskell-nix/hnix-store/compare/json-0.1.0.0...json-0.2.0.0) 2026-10-21
+
+The test suite is now much more comprenesive, uses test data from upstream Nix to make sure the JSON formats match.
+
+* JSON instance for `StorePath` now doesn't include the store dir, matching upstream.
+  (This keeps it canonical.)
+
+* Removed `deriving-aeson` and `constraints-extras` dependencies; all JSON instances are now hand-written.
+
+* New JSON instances for:
+  * `Hash`
+  * `ContentAddress`
+  * `OutputsSpec`
+  * `SingleDerivedPath`
+  * `DerivedPath`
+  * `Derivation`
+  * `Signature`
+  * `NamedSignature`
+  * `BuildTraceKey`
+  * `Realisation`
+  * `RealisationWithId`
+  * `BuildSuccessStatus`, `BuildFailureStatus`
+  * `BuildResult`
+  * `Metadata StorePath` (path-info version 3 format)
+
 # 0.1.0.0 2024-07-31
 
 * Initial release
diff --git a/hnix-store-json.cabal b/hnix-store-json.cabal
--- a/hnix-store-json.cabal
+++ b/hnix-store-json.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.2
 name:                hnix-store-json
-version:             0.1.0.0
+version:             0.2.0.0
 synopsis:            JSON serialization for core types
 description:
   Aeson instances for core types, required for remote store protocol
@@ -16,6 +16,40 @@
     CHANGELOG.md
 extra-source-files:
     README.md
+data-files:
+    upstream-libstore-data/content-address/nar.json
+    upstream-libstore-data/content-address/text.json
+    upstream-libstore-data/derived-path/multi_built_built.json
+    upstream-libstore-data/derived-path/multi_built_built_wildcard.json
+    upstream-libstore-data/derived-path/multi_opaque.json
+    upstream-libstore-data/derived-path/mutli_built.json
+    upstream-libstore-data/derived-path/single_built_built.json
+    upstream-libstore-data/derived-path/single_built.json
+    upstream-libstore-data/derived-path/single_opaque.json
+    upstream-libstore-data/derivation/dyn-dep-derivation.json
+    upstream-libstore-data/derivation/output-caFixedFlat.json
+    upstream-libstore-data/derivation/output-caFixedNAR.json
+    upstream-libstore-data/derivation/output-caFixedText.json
+    upstream-libstore-data/derivation/output-caFloating.json
+    upstream-libstore-data/derivation/output-inputAddressed.json
+    upstream-libstore-data/derivation/simple-derivation.json
+    upstream-libstore-data/outputs-spec/all.json
+    upstream-libstore-data/outputs-spec/name.json
+    upstream-libstore-data/outputs-spec/names.json
+    upstream-libstore-data/realisation/simple.json
+    upstream-libstore-data/realisation/unkeyed-simple.json
+    upstream-libstore-data/realisation/unkeyed-with-signature.json
+    upstream-libstore-data/realisation/with-signature.json
+    upstream-libstore-data/build-result/not-deterministic.json
+    upstream-libstore-data/build-result/output-rejected.json
+    upstream-libstore-data/build-result/success.json
+    upstream-libstore-data/store-path/simple.json
+    upstream-libstore-data/path-info/json-3/empty_impure.json
+    upstream-libstore-data/path-info/json-3/empty_pure.json
+    upstream-libstore-data/path-info/json-3/impure.json
+    upstream-libstore-data/path-info/json-3/pure.json
+    upstream-libutil-data/hash/sha256.json
+    upstream-libutil-data/hash/sha512.json
 
 common commons
   ghc-options:  -Wall
@@ -25,6 +59,7 @@
     , DeriveGeneric
     , DerivingVia
     , FlexibleInstances
+    , ImportQualifiedPost
     , LambdaCase
     , RecordWildCards
     , StandaloneDeriving
@@ -38,10 +73,16 @@
   build-depends:
       base >=4.12 && <5
     , hnix-store-core >= 0.8
+
     , aeson >= 2.0 && < 3.0
     , attoparsec
-    , deriving-aeson >= 0.2
+    , containers
+    , crypton
+    , dependent-sum
+    , monoidal-containers
     , text
+    , these
+    , time
   hs-source-dirs:      src
 
 test-suite json
@@ -49,7 +90,21 @@
   type: exitcode-stdio-1.0
   main-is: Spec.hs
   other-modules:
+    BuildResultSpec
+    ContentAddressSpec
+    DerivationSpec
+    DerivedPathSpec
+    HashSpec
     JSONSpec
+    OutputsSpecSpec
+    PathInfoSpec
+    RealisationSpec
+    SingleDerivedPathSpec
+    StorePathSpec
+    UpstreamData
+    Paths_hnix_store_json
+  autogen-modules:
+    Paths_hnix_store_json
   hs-source-dirs:
     tests
   build-tool-depends:
@@ -59,7 +114,16 @@
     , hnix-store-core
     , hnix-store-json
     , hnix-store-tests
+
     , aeson
+    , bytestring
     , containers
-    , data-default-class
+    , crypton
+    , dependent-sum
     , hspec
+    , monoidal-containers
+    , some
+    , these
+    , time
+    , unordered-containers
+    , vector
diff --git a/src/System/Nix/JSON.hs b/src/System/Nix/JSON.hs
--- a/src/System/Nix/JSON.hs
+++ b/src/System/Nix/JSON.hs
@@ -1,4 +1,6 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MonoLocalBinds #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {-|
 Description : JSON serialization
@@ -7,28 +9,45 @@
 providing (From|To)JSON for Realisation type
 which is required for `-remote`.
 -}
-module System.Nix.JSON where
+module System.Nix.JSON
+  ( HashJSON(..)
+  ) where
 
+import Control.Applicative ((<|>))
+import Crypto.Hash (Digest)
 import Data.Aeson
-import Deriving.Aeson
-import System.Nix.Base (BaseEncoding(NixBase32))
-import System.Nix.OutputName (OutputName)
-import System.Nix.Realisation (DerivationOutput, Realisation, RealisationWithId(..))
-import System.Nix.Signature (Signature)
-import System.Nix.StorePath (StoreDir(..), StorePath, StorePathName, StorePathHashPart)
-
-import qualified Data.Aeson.KeyMap
-import qualified Data.Aeson.Types
-import qualified Data.Attoparsec.Text
-import qualified Data.Char
-import qualified Data.Text
-import qualified Data.Text.Lazy
-import qualified Data.Text.Lazy.Builder
-import qualified System.Nix.Base
-import qualified System.Nix.OutputName
-import qualified System.Nix.Realisation
-import qualified System.Nix.Signature
-import qualified System.Nix.StorePath
+import Data.Aeson.Key qualified
+import Data.Aeson.KeyMap qualified
+import Data.Aeson.Types (Parser)
+import Data.Aeson.Types qualified
+import Data.Attoparsec.Text qualified
+import Data.Dependent.Sum
+import Data.Foldable (toList)
+import Data.Map.Monoidal qualified
+import Data.Map.Strict qualified
+import Data.Maybe (fromMaybe, maybeToList)
+import Data.Set qualified
+import Data.Some
+import Data.Text (Text)
+import Data.Text qualified
+import Data.These
+import Data.These.Combinators
+import Data.Time (diffUTCTime)
+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
+import System.Nix.Base qualified
+import System.Nix.Build (BuildResult(..), BuildSuccess(..), BuildFailure(..), BuildSuccessStatus(..), BuildFailureStatus(..))
+import System.Nix.ContentAddress
+import System.Nix.Derivation
+import System.Nix.DerivedPath (DerivedPath(..), OutputsSpec(..), SingleDerivedPath(..))
+import System.Nix.Hash
+import System.Nix.OutputName (OutputName, mkOutputName)
+import System.Nix.OutputName qualified
+import System.Nix.Realisation (BuildTraceKey(..), Realisation(..), RealisationWithId(..))
+import System.Nix.Signature (Signature, NamedSignature(..))
+import System.Nix.Signature qualified
+import System.Nix.StorePath (StorePath, StorePathName, StorePathHashPart, mkStorePathName, unStorePathName, parseBasePathFromText)
+import System.Nix.StorePath qualified
+import System.Nix.StorePath.Metadata (Metadata(..), StorePathTrust(..))
 
 instance ToJSON StorePathName where
   toJSON = toJSON . System.Nix.StorePath.unStorePathName
@@ -54,18 +73,8 @@
     )
 
 instance ToJSON StorePath where
-  toJSON =
-    toJSON
-    -- TODO: hacky, we need to stop requiring StoreDir for
-    -- StorePath rendering and have a distinct
-    -- types for rooted|unrooted paths
-    . Data.Text.drop 1
-    . System.Nix.StorePath.storePathToText (StoreDir mempty)
-
-  toEncoding =
-    toEncoding
-    . Data.Text.drop 1
-    . System.Nix.StorePath.storePathToText (StoreDir mempty)
+  toJSON = toJSON . System.Nix.StorePath.storePathBaseToText
+  toEncoding = toEncoding . System.Nix.StorePath.storePathBaseToText
 
 instance FromJSON StorePath where
   parseJSON =
@@ -73,53 +82,174 @@
     ( either
         (fail . show)
         pure
-    . System.Nix.StorePath.parsePathFromText (StoreDir mempty)
-    . Data.Text.cons '/'
+    . parseBasePathFromText
     )
 
-instance ToJSON (DerivationOutput OutputName) where
-  toJSON =
-    toJSON
-    . Data.Text.Lazy.toStrict
-    . Data.Text.Lazy.Builder.toLazyText
-    . System.Nix.Realisation.derivationOutputBuilder
-        System.Nix.OutputName.unOutputName
+instance ToJSONKey StorePath where
+  toJSONKey = Data.Aeson.Types.toJSONKeyText System.Nix.StorePath.storePathBaseToText
 
-  toEncoding =
-    toEncoding
-    . Data.Text.Lazy.toStrict
-    . Data.Text.Lazy.Builder.toLazyText
-    . System.Nix.Realisation.derivationOutputBuilder
-        System.Nix.OutputName.unOutputName
+instance FromJSONKey StorePath where
+  fromJSONKey = FromJSONKeyTextParser $
+    either (fail . show) pure . parseBasePathFromText
 
-instance ToJSONKey (DerivationOutput OutputName) where
-  toJSONKey =
-    Data.Aeson.Types.toJSONKeyText
-    $ Data.Text.Lazy.toStrict
-    . Data.Text.Lazy.Builder.toLazyText
-    . System.Nix.Realisation.derivationOutputBuilder
-        System.Nix.OutputName.unOutputName
+deriving newtype instance FromJSON DerivedPathMap
+deriving newtype instance ToJSON DerivedPathMap
 
-instance FromJSON (DerivationOutput OutputName) where
-  parseJSON =
-    withText "DerivationOutput OutputName"
-    ( either
-        (fail . show)
-        pure
-    . System.Nix.Realisation.derivationOutputParser
-        System.Nix.OutputName.mkOutputName
-    )
+instance FromJSON ChildNode where
+  parseJSON = withObject "ChildNode" $ \obj -> do
+    outputs <- obj .: "outputs"
+    dynamicOutputs <- obj .: "dynamicOutputs"
+    ChildNode <$> case (Data.Set.null outputs, Data.Map.Monoidal.null dynamicOutputs) of
+      (False, True) -> pure $ This outputs
+      (True, False) -> pure $ That dynamicOutputs
+      (False, False) -> pure $ These outputs dynamicOutputs
+      (True, True) -> fail "outputs and dynamic outputs cannot both be empty"
 
-instance FromJSONKey (DerivationOutput OutputName) where
-  fromJSONKey =
-    FromJSONKeyTextParser
-    ( either
-        (fail . show)
-        pure
-    . System.Nix.Realisation.derivationOutputParser
-        System.Nix.OutputName.mkOutputName
-    )
+instance ToJSON ChildNode where
+  toJSON (ChildNode cn) = object
+    [ "outputs" .= fromMaybe Data.Set.empty (justHere cn)
+    , "dynamicOutputs" .= fromMaybe Data.Map.Monoidal.empty (justThere cn)
+    ]
 
+-- | Input-addressed derivation output JSON instance
+instance FromJSON InputAddressedDerivationOutput where
+  parseJSON = withObject "InputAddressedDerivationOutput" $ \obj ->
+    InputAddressedDerivationOutput <$> obj .: "path"
+
+instance ToJSON InputAddressedDerivationOutput where
+  toJSON (InputAddressedDerivationOutput path) =
+    object ["path" .= path]
+
+-- | Fixed derivation output JSON instance
+instance FromJSON FixedDerivationOutput where
+  parseJSON = withObject "FixedDerivationOutput" $ \obj -> do
+    method <- obj .: "method" >>= either fail pure . textToMethod
+    HashJSON hash <- obj .: "hash"
+    pure $ FixedDerivationOutput method hash
+
+instance ToJSON FixedDerivationOutput where
+  toJSON (FixedDerivationOutput method hash) =
+    object
+      [ "method" .= methodToText method
+      , "hash" .= HashJSON hash
+      ]
+
+-- | Content-addressed derivation output JSON instance
+instance FromJSON ContentAddressedDerivationOutput where
+  parseJSON = withObject "ContentAddressedDerivationOutput" $ \obj -> do
+    method <- obj .: "method" >>= either fail pure . textToMethod
+    hashAlgo <- obj .: "hashAlgo" >>= either fail pure . textToAlgo
+    pure $ ContentAddressedDerivationOutput method hashAlgo
+
+instance ToJSON ContentAddressedDerivationOutput where
+  toJSON (ContentAddressedDerivationOutput method (Some hashAlgo)) =
+    object
+      [ "method" .= methodToText method
+      , "hashAlgo" .= algoToText hashAlgo
+      ]
+
+-- Helper to parse DerivationOutputs from JSON
+parseDerivationOutputs :: Object -> Parser DerivationOutputs
+parseDerivationOutputs obj = do
+  let outputPairs = Data.Aeson.KeyMap.toList obj
+  case outputPairs of
+    [] -> pure $ DerivationType_InputAddressing :=> mempty
+    (_, firstVal) : _ -> do
+      -- Parse the first output to determine the derivation type
+      withObject "first output" (\first -> do
+        -- Determine type by checking which fields are present
+        let hasPath = Data.Aeson.KeyMap.member "path" first
+            hasMethod = Data.Aeson.KeyMap.member "method" first
+            hasHash = Data.Aeson.KeyMap.member "hash" first
+            hasHashAlgo = Data.Aeson.KeyMap.member "hashAlgo" first
+
+        case (hasPath, hasMethod, hasHash, hasHashAlgo) of
+          (True, False, False, False) -> do
+            -- Input-addressed
+            outputs <- Data.Map.Strict.fromList <$> mapM (\(k, v) -> do
+              outputName <- either (fail . show) pure $ mkOutputName $ Data.Aeson.Key.toText k
+              output <- parseJSON v
+              pure (outputName, output)) outputPairs
+            pure $ DerivationType_InputAddressing :=> outputs
+          (False, True, True, False) -> do
+            -- Fixed
+            outputs <- Data.Map.Strict.fromList <$> mapM (\(k, v) -> do
+              outputName <- either (fail . show) pure $ mkOutputName $ Data.Aeson.Key.toText k
+              output <- parseJSON v
+              pure (outputName, output)) outputPairs
+            pure $ DerivationType_Fixed :=> outputs
+          (False, True, False, True) -> do
+            -- Content-addressed
+            outputs <- Data.Map.Strict.fromList <$> mapM (\(k, v) -> do
+              outputName <- either (fail . show) pure $ mkOutputName $ Data.Aeson.Key.toText k
+              output <- parseJSON v
+              pure (outputName, output)) outputPairs
+            pure $ DerivationType_ContentAddressing :=> outputs
+          _ -> fail $ "Invalid output format: " <> show (hasPath, hasMethod, hasHash, hasHashAlgo)
+        ) firstVal
+
+-- Helper to serialize DerivationOutputs to JSON
+derivationOutputsToJSON :: DerivationOutputs -> Value
+derivationOutputsToJSON (ty :=> outputs) = case ty of
+  DerivationType_InputAddressing -> toJSON outputs
+  DerivationType_Fixed -> toJSON outputs
+  DerivationType_ContentAddressing -> toJSON outputs
+
+instance FromJSONKey StorePathName where
+  fromJSONKey = FromJSONKeyTextParser $ either (fail . show) pure . mkStorePathName
+
+instance ToJSONKey StorePathName where
+  toJSONKey = Data.Aeson.Types.toJSONKeyText unStorePathName
+
+deriving newtype instance FromJSON OutputName
+deriving newtype instance ToJSON OutputName
+deriving newtype instance FromJSONKey OutputName
+deriving newtype instance ToJSONKey OutputName
+
+-- | TODO: hacky, we need to stop assuming StoreDir for
+-- StorePath to and from JSON
+instance FromJSON Derivation where
+  parseJSON = withObject "Derivation" $ \v -> do
+    name <- v .: "name"
+    inputs <- v .: "inputs" >>= \inputsObj -> DerivationInputs
+      <$> inputsObj .: "srcs"
+      <*> inputsObj .: "drvs"
+    outputs <- v .: "outputs" >>= parseDerivationOutputs
+    Derivation name outputs
+      <$> pure inputs
+      <*> v .: "system"
+      <*> v .: "builder"
+      <*> v .: "args"
+      <*> v .: "env"
+
+instance ToJSON Derivation where
+  toJSON (Derivation name outputs (DerivationInputs inputSrcs inputDrvs) system builder args env) =
+    object [ "version" .= (4 :: Int)
+           , "name" .= name
+           , "outputs" .= derivationOutputsToJSON outputs
+           , "inputs" .= object
+              [ "srcs" .= inputSrcs
+              , "drvs" .= inputDrvs
+              ]
+           , "system" .= system
+           , "builder" .= builder
+           , "args" .= args
+           , "env" .= env
+           ]
+
+instance ToJSON BuildTraceKey where
+  toJSON btk =
+    object
+      [ "drvPath" .= buildTraceKeyDrvPath btk
+      , "outputName" .= buildTraceKeyOutput btk
+      ]
+
+instance FromJSON BuildTraceKey where
+  parseJSON = withObject "BuildTraceKey" $ \o ->
+    BuildTraceKey
+      <$> o .: "drvPath"
+      <*> o .: "outputName"
+
 instance ToJSON Signature where
   toJSON = toJSON . System.Nix.Signature.signatureToText
   toEncoding = toEncoding . System.Nix.Signature.signatureToText
@@ -134,43 +264,273 @@
         System.Nix.Signature.signatureParser
     )
 
-data LowerLeading
-instance StringModifier LowerLeading where
-  getStringModifier "" = ""
-  getStringModifier (c:xs) = Data.Char.toLower c : xs
+instance ToJSON NamedSignature where
+  toJSON ns =
+    object
+      [ "keyName" .= publicKey ns
+      , "sig" .= sig ns
+      ]
 
-deriving
-  via CustomJSON
-    '[FieldLabelModifier
-       '[ StripPrefix "realisation"
-        , LowerLeading
-        , Rename "dependencies" "dependentRealisations"
-        ]
-     ] Realisation
-  instance ToJSON Realisation
-deriving
-  via CustomJSON
-    '[FieldLabelModifier
-       '[ StripPrefix "realisation"
-        , LowerLeading
-        , Rename "dependencies" "dependentRealisations"
-        ]
-     ] Realisation
-  instance FromJSON Realisation
+instance FromJSON NamedSignature where
+  parseJSON = withObject "NamedSignature" $ \o -> do
+    keyName <- o .: "keyName"
+    sigVal <- o .: "sig"
+    pure $ NamedSignature { publicKey = keyName, sig = sigVal }
 
+-- | Needed to avoid overlapping instances
+newtype HashJSON = HashJSON { unHashJSON :: DSum HashAlgo Digest }
+  deriving (Eq, Show)
+
+instance ToJSON HashJSON where
+  toJSON (HashJSON (algo :=> digest)) =
+    -- SRI format: "algo-base64hash"
+    toJSON $ algoToText algo <> "-" <> encodeDigestWith Base64 digest
+
+instance FromJSON HashJSON where
+  parseJSON = withText "HashJSON" $ \t -> do
+    -- SRI format: "algo-base64hash"
+    let (algoText, rest) = Data.Text.breakOn "-" t
+    case Data.Text.uncons rest of
+      Nothing -> fail "HashJSON: missing '-' separator"
+      Just ('-', _hashText) ->
+        either fail (pure . HashJSON) $ mkNamedDigest algoText t
+      _ -> fail "HashJSON: missing '-' separator"
+
+instance ToJSON ContentAddress where
+  toJSON (ContentAddress method digest) =
+    object
+      [ "hash" .= HashJSON digest
+      , "method" .= methodToText method
+      ]
+
+instance FromJSON ContentAddress where
+  parseJSON = withObject "ContentAddress" $ \obj -> do
+    HashJSON digest <- obj .: "hash"
+    methodText <- obj .: "method"
+    method <- either fail pure $ textToMethod methodText
+    pure $ ContentAddress method digest
+
+-- | OutputsSpec encodes as an array. The wildcard "*" is a singleton
+-- array ["*"] to match upstream Nix JSON format.
+instance ToJSON OutputsSpec where
+  toJSON OutputsSpec_All = toJSON ["*" :: Text]
+  toJSON (OutputsSpec_Names names) = toJSON $ Data.Set.toList names
+
+instance FromJSON OutputsSpec where
+  parseJSON = withArray "OutputsSpec" $ \arr -> do
+    outputs <- mapM parseJSON (toList arr)
+    if outputs == ["*" :: Text]
+      then pure OutputsSpec_All
+      else do
+        names <- mapM (either (fail . show) pure . System.Nix.OutputName.mkOutputName) outputs
+        pure $ OutputsSpec_Names (Data.Set.fromList names)
+
+instance ToJSON SingleDerivedPath where
+  toJSON (SingleDerivedPath_Opaque path) = toJSON path
+  toJSON (SingleDerivedPath_Built drvPath output) =
+    object
+      [ "drvPath" .= toJSON drvPath
+      , "output" .= output
+      ]
+
+instance FromJSON SingleDerivedPath where
+  parseJSON v = parseOpaque v <|> parseBuilt v
+    where
+      parseOpaque = fmap SingleDerivedPath_Opaque . parseJSON
+      parseBuilt = withObject "SingleDerivedPath_Built" $ \obj ->
+        SingleDerivedPath_Built
+          <$> obj .: "drvPath"
+          <*> obj .: "output"
+
+instance ToJSON DerivedPath where
+  toJSON (DerivedPath_Opaque path) = toJSON path
+  toJSON (DerivedPath_Built drvPath outputs) =
+    object
+      [ "drvPath" .= toJSON drvPath
+      , "outputs" .= outputs
+      ]
+
+instance FromJSON DerivedPath where
+  parseJSON v = parseOpaque v <|> parseBuilt v
+    where
+      parseOpaque = fmap DerivedPath_Opaque . parseJSON
+      parseBuilt = withObject "DerivedPath_Built" $ \obj ->
+        DerivedPath_Built
+          <$> obj .: "drvPath"
+          <*> obj .: "outputs"
+
+instance ToJSON Realisation where
+  toJSON r =
+    object
+      [ "outPath" .= realisationOutPath r
+      , "signatures" .= realisationSignatures r
+      ]
+
+instance FromJSON Realisation where
+  parseJSON = withObject "Realisation" $ \o ->
+    Realisation
+      <$> o .: "outPath"
+      <*> o .:? "signatures" .!= mempty
+
 -- For a keyed version of Realisation
--- we use RealisationWithId (DerivationOutput OutputName, Realisation)
--- instead of Realisation.id :: (DerivationOutput OutputName)
+-- we use RealisationWithId (BuildTraceKey, Realisation)
+-- instead of Realisation.id :: BuildTraceKey
 -- field.
 instance ToJSON RealisationWithId where
   toJSON (RealisationWithId (drvOut, r)) =
-    case toJSON r of
-      Object o -> Object $ Data.Aeson.KeyMap.insert "id" (toJSON drvOut) o
-      _ -> error "absurd"
+    object
+      [ "key" .= drvOut
+      , "value" .= r
+      ]
 
 instance FromJSON RealisationWithId where
-  parseJSON v@(Object o) = do
-    r <- parseJSON @Realisation v
-    drvOut <- o .: "id"
+  parseJSON = withObject "RealisationWithId" $ \o -> do
+    drvOut <- o .: "key"
+    r <- o .: "value"
     pure (RealisationWithId (drvOut, r))
-  parseJSON x = fail $ "Expected Object but got " ++ show x
+
+-- BuildSuccessStatus enum to/from JSON as strings
+instance ToJSON BuildSuccessStatus where
+  toJSON BuildSuccessStatus_Built = String "Built"
+  toJSON BuildSuccessStatus_Substituted = String "Substituted"
+  toJSON BuildSuccessStatus_AlreadyValid = String "AlreadyValid"
+  toJSON BuildSuccessStatus_ResolvesToAlreadyValid = String "ResolvesToAlreadyValid"
+
+instance FromJSON BuildSuccessStatus where
+  parseJSON = withText "BuildSuccessStatus" $ \case
+    "Built" -> pure BuildSuccessStatus_Built
+    "Substituted" -> pure BuildSuccessStatus_Substituted
+    "AlreadyValid" -> pure BuildSuccessStatus_AlreadyValid
+    "ResolvesToAlreadyValid" -> pure BuildSuccessStatus_ResolvesToAlreadyValid
+    other -> fail $ "Unknown BuildSuccessStatus: " ++ Data.Text.unpack other
+
+-- BuildFailureStatus enum to/from JSON as strings
+instance ToJSON BuildFailureStatus where
+  toJSON BuildFailureStatus_PermanentFailure = String "PermanentFailure"
+  toJSON BuildFailureStatus_InputRejected = String "InputRejected"
+  toJSON BuildFailureStatus_OutputRejected = String "OutputRejected"
+  toJSON BuildFailureStatus_TransientFailure = String "TransientFailure"
+  toJSON BuildFailureStatus_CachedFailure = String "CachedFailure"
+  toJSON BuildFailureStatus_TimedOut = String "TimedOut"
+  toJSON BuildFailureStatus_MiscFailure = String "MiscFailure"
+  toJSON BuildFailureStatus_DependencyFailed = String "DependencyFailed"
+  toJSON BuildFailureStatus_LogLimitExceeded = String "LogLimitExceeded"
+  toJSON BuildFailureStatus_NotDeterministic = String "NotDeterministic"
+  toJSON BuildFailureStatus_NoSubstituters = String "NoSubstituters"
+  toJSON BuildFailureStatus_HashMismatch = String "HashMismatch"
+
+instance FromJSON BuildFailureStatus where
+  parseJSON = withText "BuildFailureStatus" $ \case
+    "PermanentFailure" -> pure BuildFailureStatus_PermanentFailure
+    "InputRejected" -> pure BuildFailureStatus_InputRejected
+    "OutputRejected" -> pure BuildFailureStatus_OutputRejected
+    "TransientFailure" -> pure BuildFailureStatus_TransientFailure
+    "CachedFailure" -> pure BuildFailureStatus_CachedFailure
+    "TimedOut" -> pure BuildFailureStatus_TimedOut
+    "MiscFailure" -> pure BuildFailureStatus_MiscFailure
+    "DependencyFailed" -> pure BuildFailureStatus_DependencyFailed
+    "LogLimitExceeded" -> pure BuildFailureStatus_LogLimitExceeded
+    "NotDeterministic" -> pure BuildFailureStatus_NotDeterministic
+    "NoSubstituters" -> pure BuildFailureStatus_NoSubstituters
+    "HashMismatch" -> pure BuildFailureStatus_HashMismatch
+    other -> fail $ "Unknown BuildFailureStatus: " ++ Data.Text.unpack other
+
+-- BuildResult JSON instances
+instance ToJSON BuildResult where
+  toJSON (BuildResult status timesBuilt startTime stopTime cpuUser cpuSystem) =
+    case status of
+      Right (BuildSuccess successStatus builtOutputs) ->
+        object $ concat
+          [ [ "success" .= True
+            , "status" .= successStatus
+            , "builtOutputs" .= builtOutputs
+            , "timesBuilt" .= timesBuilt
+            , "startTime" .= (floor (realToFrac (diffUTCTime startTime (posixSecondsToUTCTime 0)) :: Double) :: Integer)
+            , "stopTime" .= (floor (realToFrac (diffUTCTime stopTime (posixSecondsToUTCTime 0)) :: Double) :: Integer)
+            ]
+          , maybeToList $ ("cpuUser" .=) <$> cpuUser
+          , maybeToList $ ("cpuSystem" .=) <$> cpuSystem
+          ]
+      Left (BuildFailure failureStatus errorMsg isNonDeterministic) ->
+        object
+          [ "success" .= False
+          , "status" .= failureStatus
+          , "errorMsg" .= errorMsg
+          , "isNonDeterministic" .= isNonDeterministic
+          , "timesBuilt" .= timesBuilt
+          , "startTime" .= (floor (realToFrac (diffUTCTime startTime (posixSecondsToUTCTime 0)) :: Double) :: Integer)
+          , "stopTime" .= (floor (realToFrac (diffUTCTime stopTime (posixSecondsToUTCTime 0)) :: Double) :: Integer)
+          ]
+
+instance FromJSON BuildResult where
+  parseJSON = withObject "BuildResult" $ \obj -> do
+    success <- obj .: "success"
+    timesBuilt <- obj .: "timesBuilt"
+    startTimeSeconds <- obj .: "startTime"
+    stopTimeSeconds <- obj .: "stopTime"
+    let startTime = posixSecondsToUTCTime (fromInteger startTimeSeconds)
+        stopTime = posixSecondsToUTCTime (fromInteger stopTimeSeconds)
+
+    buildResultStatus <- if success
+      then do
+        successStatus <- obj .: "status"
+        builtOutputs <- obj .:? "builtOutputs" .!= mempty
+        pure $ Right (BuildSuccess successStatus builtOutputs)
+      else do
+        failureStatus <- obj .: "status"
+        errorMsg <- obj .: "errorMsg"
+        isNonDeterministic <- obj .: "isNonDeterministic"
+        pure $ Left (BuildFailure failureStatus errorMsg isNonDeterministic)
+
+    buildResultCpuUser <- obj .:? "cpuUser"
+    buildResultCpuSystem <- obj .:? "cpuSystem"
+
+    pure BuildResult
+      { buildResultStatus = buildResultStatus
+      , buildResultTimesBuilt = timesBuilt
+      , buildResultStartTime = startTime
+      , buildResultStopTime = stopTime
+      , buildResultCpuUser = buildResultCpuUser
+      , buildResultCpuSystem = buildResultCpuSystem
+      }
+
+-- | Metadata (path-info) JSON, version 3 format
+instance ToJSON (Metadata StorePath) where
+  toJSON m = object
+    [ "version" .= (3 :: Int)
+    , "storeDir" .= ("/nix/store" :: Text)
+    , "narHash" .= HashJSON (metadataNarHash m)
+    , "narSize" .= fromMaybe 0 (metadataNarBytes m)
+    , "references" .= metadataReferences m
+    , "ca" .= metadataContentAddress m
+    , "deriver" .= metadataDeriverPath m
+    , "registrationTime" .=
+        let t = metadataRegistrationTime m
+        in if t == posixSecondsToUTCTime 0
+           then Nothing @Integer
+           else Just (floor (realToFrac (diffUTCTime t (posixSecondsToUTCTime 0)) :: Double) :: Integer)
+    , "signatures" .= metadataSigs m
+    , "ultimate" .= (metadataTrust m == BuiltLocally)
+    ]
+
+instance FromJSON (Metadata StorePath) where
+  parseJSON = withObject "Metadata" $ \o -> do
+    HashJSON narHash <- o .: "narHash"
+    narSize <- o .: "narSize"
+    references <- o .:? "references" .!= mempty
+    ca <- o .:? "ca"
+    deriver <- o .:? "deriver"
+    regTime <- o .:? "registrationTime"
+    sigs <- o .:? "signatures" .!= mempty
+    ultimate <- o .:? "ultimate" .!= False
+    pure Metadata
+      { metadataDeriverPath = deriver
+      , metadataNarHash = narHash
+      , metadataReferences = references
+      , metadataRegistrationTime =
+          maybe (posixSecondsToUTCTime 0) (posixSecondsToUTCTime . fromInteger) regTime
+      , metadataNarBytes = if narSize == (0 :: Int) then Nothing else Just (fromIntegral narSize)
+      , metadataTrust = if ultimate then BuiltLocally else BuiltElsewhere
+      , metadataSigs = sigs
+      , metadataContentAddress = ca
+      }
diff --git a/tests/BuildResultSpec.hs b/tests/BuildResultSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/BuildResultSpec.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module BuildResultSpec where
+
+import Data.Aeson (eitherDecode)
+import Data.ByteString.Lazy qualified as BSL
+import Paths_hnix_store_json (getDataFileName)
+import Test.Hspec (Spec, describe, it, shouldSatisfy)
+
+import System.Nix.Build (BuildResult(..), BuildFailure(..), BuildFailureStatus(..))
+import System.Nix.JSON ()
+
+-- For success.json, we'll just check that it parses successfully
+-- The exact BuildTraceKey values are derived from the hash in the JSON
+
+-- We just check that the failures parse correctly since they don't have complex nested structures
+
+spec :: Spec
+spec = do
+  describe "upstream Nix test data" $ do
+    it "parses success.json" $ do
+      path <- getDataFileName "upstream-libstore-data/build-result/success.json"
+      json <- BSL.readFile path
+      let result = eitherDecode json :: Either String BuildResult
+      result `shouldSatisfy` \case
+        Right (BuildResult (Right _) 3 _ _ (Just 500000000) (Just 604000000)) -> True
+        _ -> False
+
+    it "parses not-deterministic.json" $ do
+      path <- getDataFileName "upstream-libstore-data/build-result/not-deterministic.json"
+      json <- BSL.readFile path
+      let result = eitherDecode json :: Either String BuildResult
+      result `shouldSatisfy` \case
+        Right (BuildResult (Left (BuildFailure BuildFailureStatus_NotDeterministic "no idea why" False)) 1 _ _ Nothing Nothing) -> True
+        _ -> False
+
+    it "parses output-rejected.json" $ do
+      path <- getDataFileName "upstream-libstore-data/build-result/output-rejected.json"
+      json <- BSL.readFile path
+      let result = eitherDecode json :: Either String BuildResult
+      result `shouldSatisfy` \case
+        Right (BuildResult (Left (BuildFailure BuildFailureStatus_OutputRejected "no idea why" False)) 3 _ _ Nothing Nothing) -> True
+        _ -> False
diff --git a/tests/ContentAddressSpec.hs b/tests/ContentAddressSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/ContentAddressSpec.hs
@@ -0,0 +1,24 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module ContentAddressSpec where
+
+import Test.Hspec (Spec, describe)
+import Test.Hspec.Nix (forceRight)
+import UpstreamData (parsesUpstream)
+
+import System.Nix.ContentAddress (ContentAddress(..), ContentAddressMethod(..))
+import System.Nix.Hash (mkNamedDigest)
+import System.Nix.JSON ()
+
+spec :: Spec
+spec = do
+  describe "upstream Nix test data" $ do
+    parsesUpstream "upstream-libstore-data/content-address" "nar.json" $
+      ContentAddress
+        ContentAddressMethod_NixArchive
+        (forceRight $ mkNamedDigest "sha256" "9vLqj0XYoFfJVmoz+ZR02i5camYE1zYSFlDicwxvsKM=")
+
+    parsesUpstream "upstream-libstore-data/content-address" "text.json" $
+      ContentAddress
+        ContentAddressMethod_Text
+        (forceRight $ mkNamedDigest "sha256" "8OTC92xYkW7CWPJGhRvqCR0U1CR6L8PhhpRGGxgW4Ts=")
diff --git a/tests/DerivationSpec.hs b/tests/DerivationSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/DerivationSpec.hs
@@ -0,0 +1,151 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications #-}
+module DerivationSpec where
+
+import Crypto.Hash (SHA256)
+import Data.Aeson (eitherDecode)
+import Data.ByteString.Lazy qualified as BSL
+import Data.Dependent.Sum (DSum(..))
+import Data.Map.Monoidal qualified as MonoidalMap
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Some (Some(..))
+import Data.These (These(..))
+import Data.Vector qualified as Vector
+import Paths_hnix_store_json (getDataFileName)
+import Test.Hspec (Spec, describe, it, shouldBe)
+import Test.Hspec.Nix (forceRight)
+
+import System.Nix.Base (BaseEncoding(..))
+import System.Nix.ContentAddress (ContentAddressMethod(..))
+import System.Nix.Derivation
+  ( ChildNode(..)
+  , Derivation
+  , Derivation'(..)
+  , DerivationInputs(..)
+  , DerivedPathMap(..)
+  , DerivationType(..)
+  , InputAddressedDerivationOutput(..)
+  , FixedDerivationOutput(..)
+  , ContentAddressedDerivationOutput(..)
+  )
+import System.Nix.Hash (HashAlgo(..), decodeDigestWith)
+import System.Nix.JSON ()
+import System.Nix.OutputName qualified
+import System.Nix.StorePath (mkStorePathName, parseBasePathFromText)
+
+simpleDerivation :: Derivation
+simpleDerivation = Derivation
+  { name = forceRight $ mkStorePathName "simple-derivation"
+  , outputs = DerivationType_InputAddressing :=> mempty
+  , inputs = DerivationInputs
+      { srcs = Set.singleton $ forceRight $ parseBasePathFromText "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
+      , drvs = DerivedPathMap $ MonoidalMap.singleton
+          (forceRight $ parseBasePathFromText "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv")
+          (ChildNode $ This $ Set.fromList
+            [ forceRight $ System.Nix.OutputName.mkOutputName "cat"
+            , forceRight $ System.Nix.OutputName.mkOutputName "dog"
+            ])
+      }
+  , platform = "wasm-sel4"
+  , builder = "foo"
+  , args = Vector.fromList ["bar", "baz"]
+  , env = Map.fromList [("BIG_BAD", "WOLF")]
+  }
+
+dynDepDerivation :: Derivation
+dynDepDerivation = Derivation
+  { name = forceRight $ mkStorePathName "dyn-dep-derivation"
+  , outputs = DerivationType_InputAddressing :=> mempty
+  , inputs = DerivationInputs
+      { srcs = Set.singleton $ forceRight $ parseBasePathFromText "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
+      , drvs = DerivedPathMap $ MonoidalMap.singleton
+          (forceRight $ parseBasePathFromText "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv")
+          (ChildNode $ These
+            (Set.fromList
+              [ forceRight $ System.Nix.OutputName.mkOutputName "cat"
+              , forceRight $ System.Nix.OutputName.mkOutputName "dog"
+              ])
+            (MonoidalMap.fromList
+              [ ( forceRight $ System.Nix.OutputName.mkOutputName "cat"
+                , ChildNode $ This $ Set.singleton $ forceRight $ System.Nix.OutputName.mkOutputName "kitten"
+                )
+              , ( forceRight $ System.Nix.OutputName.mkOutputName "goose"
+                , ChildNode $ This $ Set.singleton $ forceRight $ System.Nix.OutputName.mkOutputName "gosling"
+                )
+              ])
+          )
+      }
+  , platform = "wasm-sel4"
+  , builder = "foo"
+  , args = Vector.fromList ["bar", "baz"]
+  , env = Map.fromList [("BIG_BAD", "WOLF")]
+  }
+
+spec :: Spec
+spec = do
+  describe "upstream Nix test data" $ do
+    describe "full derivations" $ do
+      it "parses simple-derivation.json" $ do
+        path <- getDataFileName "upstream-libstore-data/derivation/simple-derivation.json"
+        json <- BSL.readFile path
+        eitherDecode json `shouldBe` Right simpleDerivation
+
+      it "parses dyn-dep-derivation.json" $ do
+        path <- getDataFileName "upstream-libstore-data/derivation/dyn-dep-derivation.json"
+        json <- BSL.readFile path
+        eitherDecode json `shouldBe` Right dynDepDerivation
+
+    describe "output types" $ do
+      it "parses output-inputAddressed.json" $ do
+        path <- getDataFileName "upstream-libstore-data/derivation/output-inputAddressed.json"
+        json <- BSL.readFile path
+        output <- case eitherDecode json of
+          Right output -> pure output
+          Left err -> fail err
+
+        let expectedPath = forceRight $ parseBasePathFromText "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
+
+        output `shouldBe` InputAddressedDerivationOutput expectedPath
+
+      it "parses output-caFixedFlat.json" $ do
+        path <- getDataFileName "upstream-libstore-data/derivation/output-caFixedFlat.json"
+        json <- BSL.readFile path
+        output <- case eitherDecode json of
+          Right output -> pure output
+          Left err -> fail err
+
+        let expectedHash = either error id $ decodeDigestWith @SHA256 Base64 "iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="
+
+        output `shouldBe` FixedDerivationOutput ContentAddressMethod_Flat (HashAlgo_SHA256 :=> expectedHash)
+
+      it "parses output-caFixedNAR.json" $ do
+        path <- getDataFileName "upstream-libstore-data/derivation/output-caFixedNAR.json"
+        json <- BSL.readFile path
+        output <- case eitherDecode json of
+          Right output -> pure output
+          Left err -> fail err
+
+        let expectedHash = either error id $ decodeDigestWith @SHA256 Base64 "iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="
+
+        output `shouldBe` FixedDerivationOutput ContentAddressMethod_NixArchive (HashAlgo_SHA256 :=> expectedHash)
+
+      it "parses output-caFixedText.json" $ do
+        path <- getDataFileName "upstream-libstore-data/derivation/output-caFixedText.json"
+        json <- BSL.readFile path
+        output <- case eitherDecode json of
+          Right output -> pure output
+          Left err -> fail err
+
+        let expectedHash = either error id $ decodeDigestWith @SHA256 Base64 "iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="
+
+        output `shouldBe` FixedDerivationOutput ContentAddressMethod_Text (HashAlgo_SHA256 :=> expectedHash)
+
+      it "parses output-caFloating.json" $ do
+        path <- getDataFileName "upstream-libstore-data/derivation/output-caFloating.json"
+        json <- BSL.readFile path
+        output <- case eitherDecode json of
+          Right output -> pure output
+          Left err -> fail err
+
+        output `shouldBe` ContentAddressedDerivationOutput ContentAddressMethod_NixArchive (Some HashAlgo_SHA256)
diff --git a/tests/DerivedPathSpec.hs b/tests/DerivedPathSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/DerivedPathSpec.hs
@@ -0,0 +1,55 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module DerivedPathSpec where
+
+import Data.Set qualified
+import Test.Hspec (Spec, describe)
+import Test.Hspec.Nix (forceRight)
+import UpstreamData (parsesUpstream)
+
+import System.Nix.DerivedPath (DerivedPath(..), OutputsSpec(..), SingleDerivedPath(..))
+import System.Nix.JSON ()
+import System.Nix.OutputName qualified
+import System.Nix.StorePath (parseBasePathFromText)
+
+-- upstream-nix/src/libstore-tests/data/derived-path/multi_opaque.json
+upstreamMultiOpaque :: DerivedPath
+upstreamMultiOpaque = DerivedPath_Opaque $
+  forceRight $ parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"
+
+-- upstream-nix/src/libstore-tests/data/derived-path/mutli_built.json
+upstreamMultiBuilt :: DerivedPath
+upstreamMultiBuilt = DerivedPath_Built
+  (SingleDerivedPath_Opaque $ forceRight $ parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv")
+  (OutputsSpec_Names $ Data.Set.fromList
+    [ forceRight $ System.Nix.OutputName.mkOutputName "bar"
+    , forceRight $ System.Nix.OutputName.mkOutputName "baz"
+    ])
+
+-- upstream-nix/src/libstore-tests/data/derived-path/multi_built_built.json
+upstreamMultiBuiltBuilt :: DerivedPath
+upstreamMultiBuiltBuilt = DerivedPath_Built
+  (SingleDerivedPath_Built
+    (SingleDerivedPath_Opaque $ forceRight $ parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv")
+    (forceRight $ System.Nix.OutputName.mkOutputName "bar"))
+  (OutputsSpec_Names $ Data.Set.fromList
+    [ forceRight $ System.Nix.OutputName.mkOutputName "baz"
+    , forceRight $ System.Nix.OutputName.mkOutputName "quux"
+    ])
+
+-- upstream-nix/src/libstore-tests/data/derived-path/multi_built_built_wildcard.json
+upstreamMultiBuiltBuiltWildcard :: DerivedPath
+upstreamMultiBuiltBuiltWildcard = DerivedPath_Built
+  (SingleDerivedPath_Built
+    (SingleDerivedPath_Opaque $ forceRight $ parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv")
+    (forceRight $ System.Nix.OutputName.mkOutputName "bar"))
+  OutputsSpec_All
+
+spec :: Spec
+spec = do
+  let dir = "upstream-libstore-data/derived-path"
+  describe "upstream Nix test data" $ do
+    parsesUpstream dir "multi_opaque.json" upstreamMultiOpaque
+    parsesUpstream dir "mutli_built.json" upstreamMultiBuilt
+    parsesUpstream dir "multi_built_built.json" upstreamMultiBuiltBuilt
+    parsesUpstream dir "multi_built_built_wildcard.json" upstreamMultiBuiltBuiltWildcard
diff --git a/tests/HashSpec.hs b/tests/HashSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/HashSpec.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module HashSpec where
+
+import Test.Hspec (Spec, describe)
+import Test.Hspec.Nix (forceRight)
+import UpstreamData (parsesUpstream)
+
+import System.Nix.Hash (mkNamedDigest)
+import System.Nix.JSON (HashJSON(..))
+
+-- upstream-nix/src/libutil-tests/data/hash/sha256.json
+upstreamSHA256 :: HashJSON
+upstreamSHA256 = HashJSON $ forceRight $ mkNamedDigest "sha256" "8OTC92xYkW7CWPJGhRvqCR0U1CR6L8PhhpRGGxgW4Ts="
+
+-- upstream-nix/src/libutil-tests/data/hash/sha512.json
+-- Note: sha512.json also contains a sha256 SRI hash (same test value as sha256.json)
+upstreamSHA512 :: HashJSON
+upstreamSHA512 = HashJSON $ forceRight $ mkNamedDigest "sha256" "8OTC92xYkW7CWPJGhRvqCR0U1CR6L8PhhpRGGxgW4Ts="
+
+spec :: Spec
+spec = do
+  let dir = "upstream-libutil-data/hash"
+  describe "upstream Nix test data" $ do
+    parsesUpstream dir "sha256.json" upstreamSHA256
+    parsesUpstream dir "sha512.json" upstreamSHA512
diff --git a/tests/JSONSpec.hs b/tests/JSONSpec.hs
--- a/tests/JSONSpec.hs
+++ b/tests/JSONSpec.hs
@@ -1,26 +1,21 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 module JSONSpec where
 
-import Data.Aeson (ToJSON, FromJSON, decode, encode)
-import Data.Default.Class (Default(def))
-import Test.Hspec (Expectation, Spec, describe, it, shouldBe)
+import Data.Aeson (ToJSON, FromJSON, eitherDecode, encode)
+import Test.Hspec (Expectation, Spec, describe)
+import Test.Hspec.Nix (roundtrips)
 import Test.Hspec.QuickCheck (prop)
-import Test.Hspec.Nix (forceRight, roundtrips)
 
 import System.Nix.Arbitrary ()
+import System.Nix.ContentAddress (ContentAddress)
+import System.Nix.Derivation (Derivation)
+import System.Nix.DerivedPath (DerivedPath, OutputsSpec, SingleDerivedPath)
 import System.Nix.JSON ()
-import System.Nix.OutputName (OutputName)
-import System.Nix.Realisation (DerivationOutput(..), Realisation(..))
-import System.Nix.Signature (Signature)
+import System.Nix.Realisation (BuildTraceKey, Realisation)
+import System.Nix.Signature (Signature, NamedSignature)
 import System.Nix.StorePath (StorePath, StorePathName, StorePathHashPart)
 
-import qualified Data.Map
-import qualified Data.Set
-import qualified System.Nix.Hash
-import qualified System.Nix.OutputName
-import qualified System.Nix.Signature
-import qualified System.Nix.StorePath
-
 roundtripsJSON
   :: ( Eq a
      , Show a
@@ -29,73 +24,20 @@
      )
   => a
   -> Expectation
-roundtripsJSON = roundtrips encode decode
-
-sampleDerivationOutput :: DerivationOutput OutputName
-sampleDerivationOutput = DerivationOutput
-  { derivationOutputHash =
-      forceRight
-      $ System.Nix.Hash.mkNamedDigest
-          "sha256"
-          "1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0"
-  , derivationOutputOutput =
-      forceRight
-      $ System.Nix.OutputName.mkOutputName "foo"
-  }
-
-sampleRealisation0 :: Realisation
-sampleRealisation0 = Realisation
-  { realisationOutPath =
-      forceRight
-      $ System.Nix.StorePath.parsePath
-          def
-          "/nix/store/cdips4lakfk1qbf1x68fq18wnn3r5r14-builder.sh"
-  , realisationSignatures = mempty
-  , realisationDependencies = mempty
-  }
-
-sampleRealisation1 :: Realisation
-sampleRealisation1 = Realisation
-  { realisationOutPath =
-      forceRight
-      $ System.Nix.StorePath.parsePath
-          def
-          "/nix/store/5rwxzi7pal3qhpsyfc16gzkh939q1np6-curl-7.82.0.drv"
-  , realisationSignatures =
-      Data.Set.fromList
-      $ forceRight
-      . System.Nix.Signature.parseSignature
-      <$> [ "fW3iEMfyx6IZzGNswD54BjclfkXiYzh0xRXddrXfJ1rp1l8p1xTi9/0g2EibbwLFb6p83cwIJv5KtTGksC54CQ=="
-          , "SMjnB3mPgXYjXacU+xN24BdzXlAgGAuFnYwPddU3bhjfHBeQus/OimdIPMgR/JMKFPHXORrk7pbjv68vecTEBA=="
-          ]
-  , realisationDependencies =
-      Data.Map.fromList
-      [ ( sampleDerivationOutput
-        , forceRight
-          $ System.Nix.StorePath.parsePathFromText
-              def
-              "/nix/store/9472ijanf79nlkb5n1yh57s7867p1930-testFixed"
-        )
-      ]
-  }
+roundtripsJSON = roundtrips encode eitherDecode
 
 spec :: Spec
 spec = do
-  describe "JSON" $ do
-    describe "roundtrips" $ do
-      prop "StorePathName" $ roundtripsJSON @StorePathName
-      prop "StorePathHashPart" $ roundtripsJSON @StorePathHashPart
-      prop "StorePath" $ roundtripsJSON @StorePath
-      prop "DerivationOutput OutputName" $ roundtripsJSON @(DerivationOutput OutputName)
-      prop "Signature" $ roundtripsJSON @Signature
-      prop "Realisation" $ roundtripsJSON @Realisation
-
-    describe "ground truth" $ do
-      it "sampleDerivationOutput matches preimage" $
-        encode sampleDerivationOutput `shouldBe` "\"sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0!foo\""
-
-      it "sampleRealisation0 matches preimage" $
-        encode sampleRealisation0 `shouldBe` "{\"outPath\":\"cdips4lakfk1qbf1x68fq18wnn3r5r14-builder.sh\",\"signatures\":[],\"dependentRealisations\":{}}"
-
-      it "sampleRealisation1 matches preimage" $
-        encode sampleRealisation1 `shouldBe` "{\"outPath\":\"5rwxzi7pal3qhpsyfc16gzkh939q1np6-curl-7.82.0.drv\",\"signatures\":[\"SMjnB3mPgXYjXacU+xN24BdzXlAgGAuFnYwPddU3bhjfHBeQus/OimdIPMgR/JMKFPHXORrk7pbjv68vecTEBA==\",\"fW3iEMfyx6IZzGNswD54BjclfkXiYzh0xRXddrXfJ1rp1l8p1xTi9/0g2EibbwLFb6p83cwIJv5KtTGksC54CQ==\"],\"dependentRealisations\":{\"sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0!foo\":\"9472ijanf79nlkb5n1yh57s7867p1930-testFixed\"}}"
+  describe "roundtrips" $ do
+    prop "StorePathName" $ roundtripsJSON @StorePathName
+    prop "StorePathHashPart" $ roundtripsJSON @StorePathHashPart
+    prop "StorePath" $ roundtripsJSON @StorePath
+    prop "ContentAddress" $ roundtripsJSON @ContentAddress
+    prop "OutputsSpec" $ roundtripsJSON @OutputsSpec
+    prop "SingleDerivedPath" $ roundtripsJSON @SingleDerivedPath
+    prop "DerivedPath" $ roundtripsJSON @DerivedPath
+    prop "Derivation" $ roundtripsJSON @Derivation
+    prop "BuildTraceKey" $ roundtripsJSON @BuildTraceKey
+    prop "Signature" $ roundtripsJSON @Signature
+    prop "NamedSignature" $ roundtripsJSON @NamedSignature
+    prop "Realisation" $ roundtripsJSON @Realisation
diff --git a/tests/OutputsSpecSpec.hs b/tests/OutputsSpecSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/OutputsSpecSpec.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module OutputsSpecSpec where
+
+import Data.Set qualified
+import Test.Hspec (Spec, describe)
+import Test.Hspec.Nix (forceRight)
+import UpstreamData (parsesUpstream)
+
+import System.Nix.DerivedPath (OutputsSpec(..))
+import System.Nix.JSON ()
+import System.Nix.OutputName qualified
+
+-- upstream-nix/src/libstore-tests/data/outputs-spec/all.json
+upstreamAll :: OutputsSpec
+upstreamAll = OutputsSpec_All
+
+-- upstream-nix/src/libstore-tests/data/outputs-spec/name.json
+upstreamName :: OutputsSpec
+upstreamName = OutputsSpec_Names $
+  Data.Set.singleton $ forceRight $ System.Nix.OutputName.mkOutputName "a"
+
+-- upstream-nix/src/libstore-tests/data/outputs-spec/names.json
+upstreamNames :: OutputsSpec
+upstreamNames = OutputsSpec_Names $ Data.Set.fromList
+  [ forceRight $ System.Nix.OutputName.mkOutputName "a"
+  , forceRight $ System.Nix.OutputName.mkOutputName "b"
+  ]
+
+spec :: Spec
+spec = do
+  let dir = "upstream-libstore-data/outputs-spec"
+  describe "upstream Nix test data" $ do
+    parsesUpstream dir "all.json" upstreamAll
+    parsesUpstream dir "name.json" upstreamName
+    parsesUpstream dir "names.json" upstreamNames
diff --git a/tests/PathInfoSpec.hs b/tests/PathInfoSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/PathInfoSpec.hs
@@ -0,0 +1,95 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module PathInfoSpec where
+
+import Test.Hspec (Spec, describe)
+import Test.Hspec.Nix (forceRight)
+import UpstreamData (parsesUpstream)
+
+import Data.HashSet qualified
+import Data.Set qualified
+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
+
+import Crypto.Hash (Digest)
+import Data.Dependent.Sum (DSum)
+
+import System.Nix.ContentAddress (ContentAddress(..), ContentAddressMethod(..))
+import System.Nix.Hash (HashAlgo, mkNamedDigest)
+import System.Nix.JSON ()
+import System.Nix.Signature (NamedSignature(..))
+import System.Nix.Signature qualified
+import System.Nix.StorePath (StorePath)
+import System.Nix.StorePath qualified
+import System.Nix.StorePath.Metadata (Metadata(..), StorePathTrust(..))
+
+barPath :: StorePath
+barPath = forceRight $ System.Nix.StorePath.parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar"
+
+barDrvPath :: StorePath
+barDrvPath = forceRight $ System.Nix.StorePath.parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv"
+
+fooPath :: StorePath
+fooPath = forceRight $ System.Nix.StorePath.parseBasePathFromText "n5wkd9frr45pa74if5gpz9j7mifg27fh-foo"
+
+sig1 :: NamedSignature
+sig1 = NamedSignature
+  { publicKey = "asdf"
+  , sig = forceRight $ System.Nix.Signature.parseSignature "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
+  }
+
+sig2 :: NamedSignature
+sig2 = NamedSignature
+  { publicKey = "qwer"
+  , sig = forceRight $ System.Nix.Signature.parseSignature "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
+  }
+
+theNarHash :: DSum HashAlgo Digest
+theNarHash = forceRight $ mkNamedDigest "sha256" "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
+
+theCa :: ContentAddress
+theCa = ContentAddress
+  ContentAddressMethod_NixArchive
+  (forceRight $ mkNamedDigest "sha256" "EMIJ+giQ/gLIWoxmPKjno3zHZrxbGymgzGGyZvZBIdM=")
+
+spec :: Spec
+spec = do
+  let dir = "upstream-libstore-data/path-info/json-3"
+
+  describe "upstream Nix test data (path-info json-3)" $ do
+    parsesUpstream dir "pure.json" Metadata
+      { metadataDeriverPath = Nothing
+      , metadataNarHash = theNarHash
+      , metadataReferences = Data.HashSet.fromList [barPath, fooPath]
+      , metadataRegistrationTime = posixSecondsToUTCTime 0
+      , metadataNarBytes = Just 34878
+      , metadataTrust = BuiltElsewhere
+      , metadataSigs = mempty
+      , metadataContentAddress = Just theCa
+      }
+
+    parsesUpstream dir "impure.json" Metadata
+      { metadataDeriverPath = Just barDrvPath
+      , metadataNarHash = theNarHash
+      , metadataReferences = Data.HashSet.fromList [barPath, fooPath]
+      , metadataRegistrationTime = posixSecondsToUTCTime 23423
+      , metadataNarBytes = Just 34878
+      , metadataTrust = BuiltLocally
+      , metadataSigs = Data.Set.fromList [sig1, sig2]
+      , metadataContentAddress = Just theCa
+      }
+
+    parsesUpstream dir "empty_pure.json" emptyMetadata
+
+    parsesUpstream dir "empty_impure.json" emptyMetadata
+
+emptyMetadata :: Metadata StorePath
+emptyMetadata = Metadata
+  { metadataDeriverPath = Nothing
+  , metadataNarHash = theNarHash
+  , metadataReferences = mempty
+  , metadataRegistrationTime = posixSecondsToUTCTime 0
+  , metadataNarBytes = Nothing
+  , metadataTrust = BuiltElsewhere
+  , metadataSigs = mempty
+  , metadataContentAddress = Nothing
+  }
diff --git a/tests/RealisationSpec.hs b/tests/RealisationSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/RealisationSpec.hs
@@ -0,0 +1,79 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module RealisationSpec where
+
+import Data.Aeson (eitherDecode, encode)
+import Data.ByteString.Lazy qualified as BSL
+import Paths_hnix_store_json (getDataFileName)
+import Test.Hspec (Spec, describe, it, shouldBe, shouldSatisfy)
+import Test.Hspec.Nix (forceRight)
+import UpstreamData (parsesUpstream)
+
+import System.Nix.JSON ()
+import System.Nix.OutputName qualified
+import System.Nix.Realisation (BuildTraceKey(..), Realisation(..), RealisationWithId(..))
+import System.Nix.StorePath qualified
+
+sampleRealisation0 :: Realisation
+sampleRealisation0 = Realisation
+  { realisationOutPath =
+      forceRight
+      $ System.Nix.StorePath.parseBasePath
+          "cdips4lakfk1qbf1x68fq18wnn3r5r14-builder.sh"
+  , realisationSignatures = mempty
+  }
+
+-- upstream-nix/src/libstore-tests/data/realisation/simple.json
+upstreamSimpleRealisation :: RealisationWithId
+upstreamSimpleRealisation = RealisationWithId
+  ( BuildTraceKey
+      { buildTraceKeyDrvPath =
+          forceRight
+          $ System.Nix.StorePath.parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv"
+      , buildTraceKeyOutput =
+          forceRight
+          $ System.Nix.OutputName.mkOutputName "foo"
+      }
+  , Realisation
+      { realisationOutPath =
+          forceRight
+          $ System.Nix.StorePath.parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo"
+      , realisationSignatures = mempty
+      }
+  )
+
+-- upstream-nix/src/libstore-tests/data/realisation/unkeyed-simple.json
+upstreamUnkeyedSimple :: Realisation
+upstreamUnkeyedSimple = Realisation
+  { realisationOutPath =
+      forceRight
+      $ System.Nix.StorePath.parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo"
+  , realisationSignatures = mempty
+  }
+
+spec :: Spec
+spec = do
+  describe "ground truth" $ do
+    it "sampleRealisation0 matches preimage (relative paths)" $
+      encode sampleRealisation0 `shouldBe` "{\"outPath\":\"cdips4lakfk1qbf1x68fq18wnn3r5r14-builder.sh\",\"signatures\":[]}"
+
+  let dir = "upstream-libstore-data/realisation"
+  describe "upstream Nix test data" $ do
+    parsesUpstream dir "simple.json" upstreamSimpleRealisation
+    parsesUpstream dir "unkeyed-simple.json" upstreamUnkeyedSimple
+
+    it "parses with-signature.json" $ do
+      path <- getDataFileName "upstream-libstore-data/realisation/with-signature.json"
+      json <- BSL.readFile path
+      let result = eitherDecode json :: Either String RealisationWithId
+      result `shouldSatisfy` \case
+        Right _ -> True
+        Left _ -> False
+
+    it "parses unkeyed-with-signature.json" $ do
+      path <- getDataFileName "upstream-libstore-data/realisation/unkeyed-with-signature.json"
+      json <- BSL.readFile path
+      let result = eitherDecode json :: Either String Realisation
+      result `shouldSatisfy` \case
+        Right _ -> True
+        Left _ -> False
diff --git a/tests/SingleDerivedPathSpec.hs b/tests/SingleDerivedPathSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/SingleDerivedPathSpec.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module SingleDerivedPathSpec where
+
+import Test.Hspec (Spec, describe)
+import Test.Hspec.Nix (forceRight)
+import UpstreamData (parsesUpstream)
+
+import System.Nix.DerivedPath (SingleDerivedPath(..))
+import System.Nix.JSON ()
+import System.Nix.OutputName qualified
+import System.Nix.StorePath (parseBasePathFromText)
+
+-- upstream-nix/src/libstore-tests/data/derived-path/single_opaque.json
+upstreamSingleOpaque :: SingleDerivedPath
+upstreamSingleOpaque = SingleDerivedPath_Opaque $
+  forceRight $ parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"
+
+-- upstream-nix/src/libstore-tests/data/derived-path/single_built.json
+upstreamSingleBuilt :: SingleDerivedPath
+upstreamSingleBuilt = SingleDerivedPath_Built
+  (SingleDerivedPath_Opaque $ forceRight $ parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv")
+  (forceRight $ System.Nix.OutputName.mkOutputName "bar")
+
+-- upstream-nix/src/libstore-tests/data/derived-path/single_built_built.json
+upstreamSingleBuiltBuilt :: SingleDerivedPath
+upstreamSingleBuiltBuilt = SingleDerivedPath_Built
+  (SingleDerivedPath_Built
+    (SingleDerivedPath_Opaque $ forceRight $ parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv")
+    (forceRight $ System.Nix.OutputName.mkOutputName "bar"))
+  (forceRight $ System.Nix.OutputName.mkOutputName "baz")
+
+spec :: Spec
+spec = do
+  let dir = "upstream-libstore-data/derived-path"
+  describe "upstream Nix test data" $ do
+    parsesUpstream dir "single_opaque.json" upstreamSingleOpaque
+    parsesUpstream dir "single_built.json" upstreamSingleBuilt
+    parsesUpstream dir "single_built_built.json" upstreamSingleBuiltBuilt
diff --git a/tests/StorePathSpec.hs b/tests/StorePathSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/StorePathSpec.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module StorePathSpec where
+
+import Test.Hspec (Spec, describe)
+import Test.Hspec.Nix (forceRight)
+import UpstreamData (parsesUpstream)
+
+import System.Nix.JSON ()
+import System.Nix.StorePath (StorePath, parseBasePathFromText)
+
+-- upstream-nix/src/libstore-tests/data/store-path/simple.json
+upstreamSimple :: StorePath
+upstreamSimple = forceRight $ parseBasePathFromText "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"
+
+spec :: Spec
+spec = do
+  describe "upstream Nix test data" $ do
+    parsesUpstream "upstream-libstore-data/store-path" "simple.json" upstreamSimple
diff --git a/tests/UpstreamData.hs b/tests/UpstreamData.hs
new file mode 100644
--- /dev/null
+++ b/tests/UpstreamData.hs
@@ -0,0 +1,16 @@
+module UpstreamData
+  ( parsesUpstream
+  ) where
+
+import Data.Aeson (FromJSON, eitherDecode)
+import Data.ByteString.Lazy qualified as BSL
+import Paths_hnix_store_json (getDataFileName)
+import Test.Hspec (Spec, it, shouldBe)
+
+-- | Helper to test parsing upstream Nix test data files.
+parsesUpstream :: (Eq a, Show a, FromJSON a) => FilePath -> FilePath -> a -> Spec
+parsesUpstream dir filename expected =
+  it ("parses " <> filename) $ do
+    path <- getDataFileName (dir ++ "/" ++ filename)
+    json <- BSL.readFile path
+    eitherDecode json `shouldBe` Right expected
diff --git a/upstream-libstore-data/build-result/not-deterministic.json b/upstream-libstore-data/build-result/not-deterministic.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/build-result/not-deterministic.json
@@ -0,0 +1,9 @@
+{
+  "errorMsg": "no idea why",
+  "isNonDeterministic": false,
+  "startTime": 0,
+  "status": "NotDeterministic",
+  "stopTime": 0,
+  "success": false,
+  "timesBuilt": 1
+}
diff --git a/upstream-libstore-data/build-result/output-rejected.json b/upstream-libstore-data/build-result/output-rejected.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/build-result/output-rejected.json
@@ -0,0 +1,9 @@
+{
+  "errorMsg": "no idea why",
+  "isNonDeterministic": false,
+  "startTime": 30,
+  "status": "OutputRejected",
+  "stopTime": 50,
+  "success": false,
+  "timesBuilt": 3
+}
diff --git a/upstream-libstore-data/build-result/success.json b/upstream-libstore-data/build-result/success.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/build-result/success.json
@@ -0,0 +1,19 @@
+{
+  "builtOutputs": {
+    "bar": {
+      "outPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
+      "signatures": []
+    },
+    "foo": {
+      "outPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo",
+      "signatures": []
+    }
+  },
+  "cpuSystem": 604000000,
+  "cpuUser": 500000000,
+  "startTime": 30,
+  "status": "Built",
+  "stopTime": 50,
+  "success": true,
+  "timesBuilt": 3
+}
diff --git a/upstream-libstore-data/content-address/nar.json b/upstream-libstore-data/content-address/nar.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/content-address/nar.json
@@ -0,0 +1,4 @@
+{
+  "hash": "sha256-9vLqj0XYoFfJVmoz+ZR02i5camYE1zYSFlDicwxvsKM=",
+  "method": "nar"
+}
diff --git a/upstream-libstore-data/content-address/text.json b/upstream-libstore-data/content-address/text.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/content-address/text.json
@@ -0,0 +1,4 @@
+{
+  "hash": "sha256-8OTC92xYkW7CWPJGhRvqCR0U1CR6L8PhhpRGGxgW4Ts=",
+  "method": "text"
+}
diff --git a/upstream-libstore-data/derivation/dyn-dep-derivation.json b/upstream-libstore-data/derivation/dyn-dep-derivation.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derivation/dyn-dep-derivation.json
@@ -0,0 +1,41 @@
+{
+  "args": [
+    "bar",
+    "baz"
+  ],
+  "builder": "foo",
+  "env": {
+    "BIG_BAD": "WOLF"
+  },
+  "inputs": {
+    "drvs": {
+      "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
+        "dynamicOutputs": {
+          "cat": {
+            "dynamicOutputs": {},
+            "outputs": [
+              "kitten"
+            ]
+          },
+          "goose": {
+            "dynamicOutputs": {},
+            "outputs": [
+              "gosling"
+            ]
+          }
+        },
+        "outputs": [
+          "cat",
+          "dog"
+        ]
+      }
+    },
+    "srcs": [
+      "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
+    ]
+  },
+  "name": "dyn-dep-derivation",
+  "outputs": {},
+  "system": "wasm-sel4",
+  "version": 4
+}
diff --git a/upstream-libstore-data/derivation/output-caFixedFlat.json b/upstream-libstore-data/derivation/output-caFixedFlat.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derivation/output-caFixedFlat.json
@@ -0,0 +1,4 @@
+{
+  "hash": "sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8=",
+  "method": "flat"
+}
diff --git a/upstream-libstore-data/derivation/output-caFixedNAR.json b/upstream-libstore-data/derivation/output-caFixedNAR.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derivation/output-caFixedNAR.json
@@ -0,0 +1,4 @@
+{
+  "hash": "sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8=",
+  "method": "nar"
+}
diff --git a/upstream-libstore-data/derivation/output-caFixedText.json b/upstream-libstore-data/derivation/output-caFixedText.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derivation/output-caFixedText.json
@@ -0,0 +1,4 @@
+{
+  "hash": "sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8=",
+  "method": "text"
+}
diff --git a/upstream-libstore-data/derivation/output-caFloating.json b/upstream-libstore-data/derivation/output-caFloating.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derivation/output-caFloating.json
@@ -0,0 +1,4 @@
+{
+  "hashAlgo": "sha256",
+  "method": "nar"
+}
diff --git a/upstream-libstore-data/derivation/output-inputAddressed.json b/upstream-libstore-data/derivation/output-inputAddressed.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derivation/output-inputAddressed.json
@@ -0,0 +1,3 @@
+{
+  "path": "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
+}
diff --git a/upstream-libstore-data/derivation/simple-derivation.json b/upstream-libstore-data/derivation/simple-derivation.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derivation/simple-derivation.json
@@ -0,0 +1,28 @@
+{
+  "args": [
+    "bar",
+    "baz"
+  ],
+  "builder": "foo",
+  "env": {
+    "BIG_BAD": "WOLF"
+  },
+  "inputs": {
+    "drvs": {
+      "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
+        "dynamicOutputs": {},
+        "outputs": [
+          "cat",
+          "dog"
+        ]
+      }
+    },
+    "srcs": [
+      "c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
+    ]
+  },
+  "name": "simple-derivation",
+  "outputs": {},
+  "system": "wasm-sel4",
+  "version": 4
+}
diff --git a/upstream-libstore-data/derived-path/multi_built_built.json b/upstream-libstore-data/derived-path/multi_built_built.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derived-path/multi_built_built.json
@@ -0,0 +1,10 @@
+{
+  "drvPath": {
+    "drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
+    "output": "bar"
+  },
+  "outputs": [
+    "baz",
+    "quux"
+  ]
+}
diff --git a/upstream-libstore-data/derived-path/multi_built_built_wildcard.json b/upstream-libstore-data/derived-path/multi_built_built_wildcard.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derived-path/multi_built_built_wildcard.json
@@ -0,0 +1,9 @@
+{
+  "drvPath": {
+    "drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
+    "output": "bar"
+  },
+  "outputs": [
+    "*"
+  ]
+}
diff --git a/upstream-libstore-data/derived-path/multi_opaque.json b/upstream-libstore-data/derived-path/multi_opaque.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derived-path/multi_opaque.json
@@ -0,0 +1,1 @@
+"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"
diff --git a/upstream-libstore-data/derived-path/mutli_built.json b/upstream-libstore-data/derived-path/mutli_built.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derived-path/mutli_built.json
@@ -0,0 +1,7 @@
+{
+  "drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
+  "outputs": [
+    "bar",
+    "baz"
+  ]
+}
diff --git a/upstream-libstore-data/derived-path/single_built.json b/upstream-libstore-data/derived-path/single_built.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derived-path/single_built.json
@@ -0,0 +1,4 @@
+{
+  "drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
+  "output": "bar"
+}
diff --git a/upstream-libstore-data/derived-path/single_built_built.json b/upstream-libstore-data/derived-path/single_built_built.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derived-path/single_built_built.json
@@ -0,0 +1,7 @@
+{
+  "drvPath": {
+    "drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
+    "output": "bar"
+  },
+  "output": "baz"
+}
diff --git a/upstream-libstore-data/derived-path/single_opaque.json b/upstream-libstore-data/derived-path/single_opaque.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/derived-path/single_opaque.json
@@ -0,0 +1,1 @@
+"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"
diff --git a/upstream-libstore-data/outputs-spec/all.json b/upstream-libstore-data/outputs-spec/all.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/outputs-spec/all.json
@@ -0,0 +1,3 @@
+[
+  "*"
+]
diff --git a/upstream-libstore-data/outputs-spec/name.json b/upstream-libstore-data/outputs-spec/name.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/outputs-spec/name.json
@@ -0,0 +1,3 @@
+[
+  "a"
+]
diff --git a/upstream-libstore-data/outputs-spec/names.json b/upstream-libstore-data/outputs-spec/names.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/outputs-spec/names.json
@@ -0,0 +1,4 @@
+[
+  "a",
+  "b"
+]
diff --git a/upstream-libstore-data/path-info/json-3/empty_impure.json b/upstream-libstore-data/path-info/json-3/empty_impure.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/path-info/json-3/empty_impure.json
@@ -0,0 +1,12 @@
+{
+  "ca": null,
+  "deriver": null,
+  "narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
+  "narSize": 0,
+  "references": [],
+  "registrationTime": null,
+  "signatures": [],
+  "storeDir": "/nix/store",
+  "ultimate": false,
+  "version": 3
+}
diff --git a/upstream-libstore-data/path-info/json-3/empty_pure.json b/upstream-libstore-data/path-info/json-3/empty_pure.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/path-info/json-3/empty_pure.json
@@ -0,0 +1,8 @@
+{
+  "ca": null,
+  "narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
+  "narSize": 0,
+  "references": [],
+  "storeDir": "/nix/store",
+  "version": 3
+}
diff --git a/upstream-libstore-data/path-info/json-3/impure.json b/upstream-libstore-data/path-info/json-3/impure.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/path-info/json-3/impure.json
@@ -0,0 +1,27 @@
+{
+  "ca": {
+    "hash": "sha256-EMIJ+giQ/gLIWoxmPKjno3zHZrxbGymgzGGyZvZBIdM=",
+    "method": "nar"
+  },
+  "deriver": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv",
+  "narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
+  "narSize": 34878,
+  "references": [
+    "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
+    "n5wkd9frr45pa74if5gpz9j7mifg27fh-foo"
+  ],
+  "registrationTime": 23423,
+  "signatures": [
+    {
+      "keyName": "asdf",
+      "sig": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
+    },
+    {
+      "keyName": "qwer",
+      "sig": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
+    }
+  ],
+  "storeDir": "/nix/store",
+  "ultimate": true,
+  "version": 3
+}
diff --git a/upstream-libstore-data/path-info/json-3/pure.json b/upstream-libstore-data/path-info/json-3/pure.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/path-info/json-3/pure.json
@@ -0,0 +1,14 @@
+{
+  "ca": {
+    "hash": "sha256-EMIJ+giQ/gLIWoxmPKjno3zHZrxbGymgzGGyZvZBIdM=",
+    "method": "nar"
+  },
+  "narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
+  "narSize": 34878,
+  "references": [
+    "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
+    "n5wkd9frr45pa74if5gpz9j7mifg27fh-foo"
+  ],
+  "storeDir": "/nix/store",
+  "version": 3
+}
diff --git a/upstream-libstore-data/realisation/simple.json b/upstream-libstore-data/realisation/simple.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/realisation/simple.json
@@ -0,0 +1,10 @@
+{
+  "key": {
+    "drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv",
+    "outputName": "foo"
+  },
+  "value": {
+    "outPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo",
+    "signatures": []
+  }
+}
diff --git a/upstream-libstore-data/realisation/unkeyed-simple.json b/upstream-libstore-data/realisation/unkeyed-simple.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/realisation/unkeyed-simple.json
@@ -0,0 +1,4 @@
+{
+  "outPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo",
+  "signatures": []
+}
diff --git a/upstream-libstore-data/realisation/unkeyed-with-signature.json b/upstream-libstore-data/realisation/unkeyed-with-signature.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/realisation/unkeyed-with-signature.json
@@ -0,0 +1,9 @@
+{
+  "outPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
+  "signatures": [
+    {
+      "keyName": "asdf",
+      "sig": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
+    }
+  ]
+}
diff --git a/upstream-libstore-data/realisation/with-signature.json b/upstream-libstore-data/realisation/with-signature.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/realisation/with-signature.json
@@ -0,0 +1,15 @@
+{
+  "key": {
+    "drvPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv",
+    "outputName": "foo"
+  },
+  "value": {
+    "outPath": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv",
+    "signatures": [
+      {
+        "keyName": "asdf",
+        "sig": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
+      }
+    ]
+  }
+}
diff --git a/upstream-libstore-data/store-path/simple.json b/upstream-libstore-data/store-path/simple.json
new file mode 100644
--- /dev/null
+++ b/upstream-libstore-data/store-path/simple.json
@@ -0,0 +1,1 @@
+"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv"
diff --git a/upstream-libutil-data/hash/sha256.json b/upstream-libutil-data/hash/sha256.json
new file mode 100644
--- /dev/null
+++ b/upstream-libutil-data/hash/sha256.json
@@ -0,0 +1,1 @@
+"sha256-8OTC92xYkW7CWPJGhRvqCR0U1CR6L8PhhpRGGxgW4Ts="
diff --git a/upstream-libutil-data/hash/sha512.json b/upstream-libutil-data/hash/sha512.json
new file mode 100644
--- /dev/null
+++ b/upstream-libutil-data/hash/sha512.json
@@ -0,0 +1,1 @@
+"sha256-8OTC92xYkW7CWPJGhRvqCR0U1CR6L8PhhpRGGxgW4Ts="
