diff --git a/.gitignore b/.gitignore
index 82ad53c..de26cfe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
 /*.pdf
 dist/
 dist-newstyle/
+ci-out/
 .cabal-sandbox/
 .stack-work/
 cabal.sandbox.config
diff --git a/default.nix b/default.nix
new file mode 100644
index 0000000..2143997
--- /dev/null
+++ b/default.nix
@@ -0,0 +1 @@
+(import ./nix/all.nix {}).default.multistate
\ No newline at end of file
diff --git a/nix/.gitignore b/nix/.gitignore
new file mode 100644
index 0000000..7556e9b
--- /dev/null
+++ b/nix/.gitignore
@@ -0,0 +1,2 @@
+nixpkgs.nix
+local-extra-deps.nix
diff --git a/nix/README.md b/nix/README.md
new file mode 100644
index 0000000..2b962a0
--- /dev/null
+++ b/nix/README.md
@@ -0,0 +1,32 @@
+
+This nix setup expects the iohk haskell-nix overlay to be available/included
+when importing `<nixpkgs>`. Also, you might need a specific commit if you
+want to test against all supported ghcs (8.4 - 8.10, currently).
+
+# Useful commands:
+
+~~~~.sh
+# enter a shell for a specific build-plan
+# (cabal-solved with ghc-8.4 in this case)
+nix-shell nix/all.nix -A '"hackage-8.4".shell'
+# run tests against ghcs 8.4 through 8.10, both against hackage and stackage package sets
+nix/ci.sh
+~~~~
+
+
+# Files in this directory:
+
+all.nix          - main entrypoint into this package's nix world
+via-hackage.nix  - how to build this via cabal-solved package-set
+via-stackage.nix - how to build via stackage-based package set
+nixpkgs.nix      - optional - if you want to use a custom nixpkgs channel
+                   (the replacement needs to have haskell-nix overlay _and_
+                   the cabal-check feature enabled though!)
+local-extra-deps.nix - optional - for defining local addition deps for
+                   dev testing
+
+(plus some currently unused:)
+
+materialized  - materializations of cabal-solved build-plans
+plan.nix      - manual materialization of unsolved build-plan (used with
+                stackage snapshot to build package set)
diff --git a/nix/all.nix b/nix/all.nix
new file mode 100644
index 0000000..5ef5b44
--- /dev/null
+++ b/nix/all.nix
@@ -0,0 +1,99 @@
+let
+  importOrElse = maybePath: otherwise:
+    if builtins.pathExists maybePath then import maybePath else otherwise;
+  pkgs = importOrElse ./nixpkgs.nix
+    ( let
+        haskellNix = import (
+          builtins.fetchTarball
+            https://github.com/lspitzner/haskell.nix/archive/4ad436d66d1a553d1a36d89fcab9329f10ae36e9.tar.gz
+        ) { version = 2; };
+        nixpkgsSrc = haskellNix.sources.nixpkgs-1909;
+      in
+      import nixpkgsSrc haskellNix.nixpkgsArgs
+    );
+  gitignoreSrc = pkgs.fetchFromGitHub {
+    # owner = "hercules-ci";
+    owner = "lspitzner"; # TODO switch back to the above once PR is merged
+                         # see https://github.com/hercules-ci/gitignore.nix/pull/44
+    repo = "gitignore.nix";
+    rev = "97d53665298d2b31b79e5fe4b60edb12a6661547";
+    sha256 = "sha256:1b3z2ikpg32zsfrhv4fb17dqavgg7d4wahslxlm37w68y7adsdav";
+  };
+  inherit (import gitignoreSrc { inherit (pkgs) lib; }) gitignoreSource gitignoreFilter;
+  cleanedSource = pkgs.lib.cleanSourceWith {
+    name = "butcher";
+    src = ./..;
+    filter = p: t:
+      let baseName = baseNameOf (toString p);
+      in gitignoreFilter ./.. p t
+      && baseName != ".gitignore"
+      && baseName != "nix"
+      && baseName != "shell.nix"
+      && baseName != "default.nix";
+  };
+  localExtraDeps = importOrElse ./local-extra-deps.nix (_: []) {inherit pkgs;};
+  args = {
+    inherit pkgs;
+    inherit cleanedSource;
+    pkg-def-extras = localExtraDeps;
+  };
+  inherit (builtins) hasAttr;
+in
+assert pkgs.lib.assertMsg (hasAttr "haskell-nix" pkgs) "need iohk haskell-nix overlay!";
+let
+  versions = {
+    # "stack-8.0" = import ./via-stack.nix (args // { resolver = "lts-9.21"; });
+    # "stack-8.2" = import ./via-stack.nix (args // { resolver = "lts-11.22"; });
+    "stackage-8.4" = import ./via-stackage.nix (args // {
+      # resolver = "lts-12.26";
+      stackFile = "stack-8.4.yaml";
+    });
+    "stackage-8.6" = import ./via-stackage.nix (args // {
+      # resolver = "lts-14.27";
+      stackFile = "stack-8.6.yaml";
+    });
+    "stackage-8.8" = import ./via-stackage.nix (args // {
+      # resolver = "lts-15.12";
+      stackFile = "stack-8.8.yaml";
+    });
+    "hackage-8.4" = import ./via-hackage.nix (args // { 
+      ghc-ver = "ghc844";
+      index-state = "2020-05-01T00:00:00Z";
+      # plan-sha256 = "0s6rfanb6zxhr5zbinp7h25ahwasciwj3ambsr6zdxm1l782b3ap";
+      # materialized = ./materialized/hackage-8.4;
+      configureArgs = "--allow-newer multistate:*";
+    });
+    "hackage-8.6" = import ./via-hackage.nix (args // { 
+      ghc-ver = "ghc865";
+      index-state = "2020-05-01T00:00:00Z";
+      # plan-sha256 = "01m95xirrh00dvdxrpsx8flhcwlwcvgr3diwlnkw7lj5f3i7rfrl";
+      # materialized = ./materialized/hackage-8.6;
+      configureArgs = "--allow-newer multistate:*";
+    });
+    "hackage-8.8" = import ./via-hackage.nix (args // { 
+      ghc-ver = "ghc883";
+      index-state = "2020-05-01T00:00:00Z";
+      # plan-sha256 = "14qs7ynlf7p2qvdk8sf498y87ss5vab3ylnbpc8sacqbpv2hv4pf";
+      # materialized = ./materialized/hackage-8.8;
+      configureArgs = "--allow-newer multistate:*";
+    });
+  } // (if hasAttr "ghc8101" pkgs.haskell-nix.compiler
+    then {
+    "hackage-8.10" = import ./via-hackage.nix (args // { 
+      ghc-ver = "ghc8101";
+      index-state = "2020-06-06T00:00:00Z";
+      # index-sha256 = "1h1x65840jl6w2qvyq9csc7b3ivadr933glarnmydk2b23vw2i77";
+      # plan-sha256 = "1s8a6cb5qgf4ky5s750rzx6aa52slp1skazh8kbx0dbfjd6df7yw";
+      # materialized = ./materialized/hackage-8.10;
+      configureArgs = "--allow-newer multistate:* --constraint 'splitmix<0.1'";
+    });
+    } else builtins.trace "warn: ghc 8.10 is not avaiable, will not be tested!" {}
+  );
+  linkFarmFromDrvs = name: drvs:
+    let mkEntryFromDrv = drv: { name = drv.name; path = drv; };
+    in pkgs.linkFarm name (map mkEntryFromDrv drvs);
+in
+versions // {
+  inherit cleanedSource;
+  default = versions."stackage-8.8";
+}
\ No newline at end of file
diff --git a/nix/ci.sh b/nix/ci.sh
new file mode 100755
index 0000000..cbf3d21
--- /dev/null
+++ b/nix/ci.sh
@@ -0,0 +1,50 @@
+
+OUTDIR="ci-out"
+SUMMARY="$OUTDIR/0-summary"
+CABAL_CHECK_ATTRPATH="hackage-8.10"
+
+set -x
+
+mkdir -p "$OUTDIR"
+echo "# test summary" > "$SUMMARY"
+
+function build-one {
+  local ATTRPATH=$1
+  # nix-build --no-out-link nix/all.nix -A "\"$ATTRPATH\".butcher.components.library"\
+  #   2> >(tee "$OUTDIR/$ATTRPATH-1-build-lib.txt" >&2)
+  # (($? == 0)) || { echo "$ATTRPATH: build src failed" >> "$SUMMARY"; return 1; }
+  # nix-build --no-out-link nix/all.nix -A "\"$ATTRPATH\".butcher.components.tests"\
+  #   2> >(tee "$OUTDIR/$ATTRPATH-2-build-test.txt" >&2)
+  # (($? == 0)) || { echo "$ATTRPATH: build test failed" >> "$SUMMARY"; return 1; }
+  OUT=$(nix-build -o "$OUTDIR/$ATTRPATH-test-result.txt" nix/all.nix -A "\"$ATTRPATH\".butcher.checks.tests"\
+    2> >(tee "$OUTDIR/$ATTRPATH-build.txt" >&2))
+  (($? == 0)) || { echo "$ATTRPATH: run test failed" >> "$SUMMARY"; return 1; }
+  echo "$ATTRPATH: $(grep examples "$OUTDIR/$ATTRPATH-test-result.txt")" >> "$SUMMARY"
+}
+
+function cabal-check {
+  nix-build --no-out-link nix/all.nix -A "\"$CABAL_CHECK_ATTRPATH\".checks.cabal-check"\
+    2> >(tee "$OUTDIR/cabal-check.txt" >&2)
+  (($? == 0)) || { echo "cabal-check: failed" >> "$SUMMARY"; return 1; }
+  echo "cabal-check: success" >> "$SUMMARY"
+}
+
+find "$OUTDIR" -name "stackage*" -delete
+find "$OUTDIR" -name "hackage*" -delete
+rm "$OUTDIR/cabal-check.txt"
+CLEANEDSOURCE=$(nix-instantiate --eval --read-write-mode nix/all.nix -A "cleanedSource.outPath")
+(($? == 0)) || exit 1
+( eval "cd $CLEANEDSOURCE; find" ) > "$OUTDIR/1-cleanedSource.txt"
+
+build-one "stackage-8.4"
+build-one "stackage-8.6"
+build-one "stackage-8.8"
+
+build-one "hackage-8.4"
+build-one "hackage-8.6"
+build-one "hackage-8.8"
+build-one "hackage-8.10"
+
+cabal-check
+
+cat "$SUMMARY"
diff --git a/nix/via-hackage.nix b/nix/via-hackage.nix
new file mode 100644
index 0000000..f9c39af
--- /dev/null
+++ b/nix/via-hackage.nix
@@ -0,0 +1,58 @@
+{ pkgs
+, cleanedSource
+, pkg-def-extras ? []
+, ghc-ver
+, index-state
+, index-sha256 ? null
+, plan-sha256 ? null
+, materialized ? null
+, configureArgs ? null
+}:
+let
+  butcher-plan = pkgs.haskell-nix.importAndFilterProject (pkgs.haskell-nix.callCabalProjectToNix {
+    src = cleanedSource;
+    inherit index-state index-sha256 plan-sha256 materialized configureArgs;
+    # ghc = pkgs.haskell-nix.compiler.${ghc-ver};
+    compiler-nix-name = ghc-ver;
+  });
+in rec {
+  inherit butcher-plan pkgs;
+
+  hsPkgs = 
+    let
+    in let pkg-set = pkgs.haskell-nix.mkCabalProjectPkgSet
+              { plan-pkgs = butcher-plan.pkgs;
+                pkg-def-extras = pkg-def-extras;
+                modules = [ 
+                  { ghc.package = pkgs.haskell-nix.compiler.${ghc-ver}; }
+                ];
+              };
+    in pkg-set.config.hsPkgs;
+
+  inherit (hsPkgs) butcher;
+  inherit (hsPkgs.butcher) checks;
+  shell = hsPkgs.shellFor {
+    # Include only the *local* packages of your project.
+    packages = ps: with ps; [
+      butcher
+    ];
+
+    # Builds a Hoogle documentation index of all dependencies,
+    # and provides a "hoogle" command to search the index.
+    withHoogle = false;
+
+    # You might want some extra tools in the shell (optional).
+
+    # Some common tools can be added with the `tools` argument
+    # tools = { cabal = "3.2.0.0"; };
+    # See overlays/tools.nix for more details
+
+    # Some you may need to get some other way.
+    buildInputs = with pkgs.haskellPackages;
+      [ pkgs.haskell-nix.cabal-install ghcid bash pkgs.nix ];
+
+    # Prevents cabal from choosing alternate plans, so that
+    # *all* dependencies are provided by Nix.
+    exactDeps = true;
+  };
+}
diff --git a/nix/via-stackage.nix b/nix/via-stackage.nix
new file mode 100644
index 0000000..172a76f
--- /dev/null
+++ b/nix/via-stackage.nix
@@ -0,0 +1,89 @@
+{ pkgs
+, cleanedSource
+, stackFile
+, pkg-def-extras ? []
+}:
+let
+  # package-desc = import ./plan.nix;
+  # butcher-plan = {
+  #   inherit resolver;
+  #   extras = hackage:
+  #     { butcher = args: package-desc args // {
+  #       src = pkgs.haskell-nix.cleanSourceHaskell {
+  #         src = pkgs.haskell-nix.haskellLib.cleanGit { src = ./..; name = "butcher"; };
+  #         name = "butcher";
+  #       };
+  #     };
+  #   };
+  # };
+  # this does not work at all, does not use local package (!)
+  # butcher-plan = (pkgs.haskell-nix.importAndFilterProject (
+  #   (pkgs.haskell-nix.callStackToNix {
+  #     name = "butcher-plan";
+  #     src = ./..;
+  #     stackYamlFile = builtins.toFile "stack.yaml" ''
+  #       resolver: ${resolver}
+  #       packages:
+  #         - '.'
+  #       extra-deps: []
+  #       extra-package-dbs: []
+  #     '';
+  #     ignorePackageYaml = true;
+  #   })
+  # ));
+  cleanedSource = pkgs.haskell-nix.cleanSourceHaskell { name = "butcher-"+stackFile; src = ./..; };
+  butcher-nix = pkgs.haskell-nix.callStackToNix {
+    name = "butcher";
+    src = cleanedSource;
+    stackYaml = stackFile;
+  };
+  butcher-plan = pkgs.haskell-nix.importAndFilterProject butcher-nix;
+  # butcher-pkgs = {
+  #   inherit (butcher-plan.pkgs) modules resolver;
+  #   extras = butcher-plan.pkgs.extras ps;
+  # };
+  generatedCache = pkgs.haskell-nix.genStackCache {
+    src = cleanedSource;
+    stackYaml = stackFile;
+  };
+  hsPkgs = (pkgs.haskell-nix.mkStackPkgSet {
+    stack-pkgs = butcher-plan.pkgs;
+    pkg-def-extras = pkg-def-extras;
+    modules = pkgs.lib.singleton (pkgs.haskell-nix.mkCacheModule generatedCache);
+  }).config.hsPkgs;
+in {
+  inherit butcher-plan hsPkgs pkgs;
+  inherit (hsPkgs) butcher;
+  inherit (hsPkgs.butcher) checks;
+  shell = hsPkgs.shellFor {
+    # Include only the *local* packages of your project.
+    packages = ps: with ps; [
+      butcher
+    ];
+
+    # Builds a Hoogle documentation index of all dependencies,
+    # and provides a "hoogle" command to search the index.
+    withHoogle = false;
+
+    # You might want some extra tools in the shell (optional).
+
+    # Some common tools can be added with the `tools` argument
+    # tools = { cabal = "3.2.0.0"; };
+    # See overlays/tools.nix for more details
+
+    # Some you may need to get some other way.
+    buildInputs = with pkgs.haskellPackages;
+      [ cabal-install ghcid bash pkgs.nix ];
+
+    # Prevents cabal from choosing alternate plans, so that
+    # *all* dependencies are provided by Nix.
+    exactDeps = true;
+  };
+}
+# pkgs.haskell-nix.stackProject {
+#   src = pkgs.haskell-nix.haskellLib.cleanGit { src = ./.; name = "butcher"; };
+#   pkg-def-extras = pkg-def-extras;
+#   modules = [
+#     { doHaddock = false; }
+#   ];
+# }
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..f8a2f7e
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1 @@
+(import ./nix/all.nix {}).default.shell
diff --git a/stack-8.4.yaml b/stack-8.4.yaml
new file mode 100644
index 0000000..7ecfbda
--- /dev/null
+++ b/stack-8.4.yaml
@@ -0,0 +1,69 @@
+# Resolver to choose a 'specific' stackage snapshot or a compiler version.
+# A snapshot resolver dictates the compiler version and the set of packages
+# to be used for project dependencies. For example:
+#
+# resolver: lts-3.5
+# resolver: nightly-2015-09-21
+# resolver: ghc-7.10.2
+#
+# The location of a snapshot can be provided as a file or url. Stack assumes
+# a snapshot provided as a file might change, whereas a url resource does not.
+#
+# resolver: ./custom-snapshot.yaml
+# resolver: https://example.com/snapshots/2018-01-01.yaml
+resolver: lts-12.26
+
+# User packages to be built.
+# Various formats can be used as shown in the example below.
+#
+# packages:
+# - some-directory
+# - https://example.com/foo/bar/baz-0.0.2.tar.gz
+#   subdirs:
+#   - auto-update
+#   - wai
+packages:
+- .
+# The following packages have been ignored due to incompatibility with the
+# resolver compiler, dependency conflicts with other packages
+# or unsatisfied dependencies.
+#- .
+
+# Dependency packages to be pulled from upstream that are not in the resolver.
+# These entries can reference officially published versions as well as
+# forks / in-progress versions pinned to a git hash. For example:
+#
+# extra-deps:
+# - acme-missiles-0.3
+# - git: https://github.com/commercialhaskell/stack.git
+#   commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
+#
+extra-deps:
+- deque-0.4.2.3
+- extra-1.7.1
+- strict-list-0.1.5
+- barbies-2.0.1.0
+
+# Override default flag values for local packages and extra-deps
+# flags: {}
+
+# Extra package databases containing global packages
+# extra-package-dbs: []
+
+# Control whether we use the GHC we find on the path
+# system-ghc: true
+#
+# Require a specific version of stack, using version ranges
+# require-stack-version: -any # Default
+# require-stack-version: ">=2.1"
+#
+# Override the architecture used by stack, especially useful on Windows
+# arch: i386
+# arch: x86_64
+#
+# Extra directories used by stack for building
+# extra-include-dirs: [/path/to/dir]
+# extra-lib-dirs: [/path/to/dir]
+#
+# Allow a newer minor version of GHC than the snapshot specifies
+# compiler-check: newer-minor
diff --git a/stack-8.6.yaml b/stack-8.6.yaml
new file mode 100644
index 0000000..4f9b75e
--- /dev/null
+++ b/stack-8.6.yaml
@@ -0,0 +1,68 @@
+# Resolver to choose a 'specific' stackage snapshot or a compiler version.
+# A snapshot resolver dictates the compiler version and the set of packages
+# to be used for project dependencies. For example:
+#
+# resolver: lts-3.5
+# resolver: nightly-2015-09-21
+# resolver: ghc-7.10.2
+#
+# The location of a snapshot can be provided as a file or url. Stack assumes
+# a snapshot provided as a file might change, whereas a url resource does not.
+#
+# resolver: ./custom-snapshot.yaml
+# resolver: https://example.com/snapshots/2018-01-01.yaml
+resolver: lts-14.4
+
+# User packages to be built.
+# Various formats can be used as shown in the example below.
+#
+# packages:
+# - some-directory
+# - https://example.com/foo/bar/baz-0.0.2.tar.gz
+#   subdirs:
+#   - auto-update
+#   - wai
+packages:
+- .
+# Dependency packages to be pulled from upstream that are not in the resolver.
+# These entries can reference officially published versions as well as
+# forks / in-progress versions pinned to a git hash. For example:
+#
+# extra-deps:
+# - acme-missiles-0.3
+# - git: https://github.com/commercialhaskell/stack.git
+#   commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
+#
+# extra-deps: []
+
+extra-deps:
+- deque-0.4.2.3@sha256:7cc8ddfc77df351ff9c16e838ccdb4a89f055c80a3111e27eba8d90e8edde7d0,1853
+- strict-list-0.1.4@sha256:0fa869e2c21b710b7133e8628169f120fe6299342628edd3d5087ded299bc941,1631
+- semigroupoids-5.3.3@sha256:260b62cb8539bb988e7f551f10a45ef1c81421c0d79010e9bde9321bad4982a7,7363
+- base-orphans-0.8.1@sha256:defd0057b5db93257528d89b5b01a0fee9738e878c121c686948ac4aa5dded63,2927
+- hashable-1.3.0.0
+- unordered-containers-0.2.10.0
+
+# Override default flag values for local packages and extra-deps
+# flags: {}
+
+# Extra package databases containing global packages
+# extra-package-dbs: []
+
+# Control whether we use the GHC we find on the path
+# system-ghc: true
+#
+# Require a specific version of stack, using version ranges
+# require-stack-version: -any # Default
+# require-stack-version: ">=2.1"
+#
+# Override the architecture used by stack, especially useful on Windows
+# arch: i386
+# arch: x86_64
+#
+# Extra directories used by stack for building
+# extra-include-dirs: [/path/to/dir]
+# extra-lib-dirs: [/path/to/dir]
+#
+# Allow a newer minor version of GHC than the snapshot specifies
+# compiler-check: newer-minor
diff --git a/stack-8.8.yaml b/stack-8.8.yaml
new file mode 100644
index 0000000..8f497d2
--- /dev/null
+++ b/stack-8.8.yaml
@@ -0,0 +1,65 @@
+# Resolver to choose a 'specific' stackage snapshot or a compiler version.
+# A snapshot resolver dictates the compiler version and the set of packages
+# to be used for project dependencies. For example:
+#
+# resolver: lts-3.5
+# resolver: nightly-2015-09-21
+# resolver: ghc-7.10.2
+#
+# The location of a snapshot can be provided as a file or url. Stack assumes
+# a snapshot provided as a file might change, whereas a url resource does not.
+#
+# resolver: ./custom-snapshot.yaml
+# resolver: https://example.com/snapshots/2018-01-01.yaml
+resolver: lts-15.12
+
+# User packages to be built.
+# Various formats can be used as shown in the example below.
+#
+# packages:
+# - some-directory
+# - https://example.com/foo/bar/baz-0.0.2.tar.gz
+#   subdirs:
+#   - auto-update
+#   - wai
+packages:
+- .
+# The following packages have been ignored due to incompatibility with the
+# resolver compiler, dependency conflicts with other packages
+# or unsatisfied dependencies.
+#- .
+
+# Dependency packages to be pulled from upstream that are not in the resolver.
+# These entries can reference officially published versions as well as
+# forks / in-progress versions pinned to a git hash. For example:
+#
+# extra-deps:
+# - acme-missiles-0.3
+# - git: https://github.com/commercialhaskell/stack.git
+#   commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
+#
+# extra-deps: []
+
+# Override default flag values for local packages and extra-deps
+# flags: {}
+
+# Extra package databases containing global packages
+# extra-package-dbs: []
+
+# Control whether we use the GHC we find on the path
+# system-ghc: true
+#
+# Require a specific version of stack, using version ranges
+# require-stack-version: -any # Default
+# require-stack-version: ">=2.1"
+#
+# Override the architecture used by stack, especially useful on Windows
+# arch: i386
+# arch: x86_64
+#
+# Extra directories used by stack for building
+# extra-include-dirs: [/path/to/dir]
+# extra-lib-dirs: [/path/to/dir]
+#
+# Allow a newer minor version of GHC than the snapshot specifies
+# compiler-check: newer-minor